From a0407330a54cf43265f78f1fd8e4cf6415dc4b4c Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 17 Nov 2025 09:11:07 +0100 Subject: [PATCH 01/65] APK build job (#158) --- .github/workflows/build.yaml | 57 +++++++++++++++++++++++++++++++++- .github/workflows/release.yaml | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 45bbb5ce..ec1c8ff2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -139,8 +139,63 @@ jobs: path: "client/build/app/outputs/bundle/release/app-release.aab" retention-days: 2 + build-android-apk: + runs-on: [self-hosted, macOS] + env: + ANDROID_HOME: /Users/admin/Library/Android/sdk + ANDROID_SDK_ROOT: /Users/admin/Library/Android/sdk + defaults: + run: + working-directory: ./client + steps: + - uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: "17" + + - name: Setup flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.32.7 + + - name: Install Android SDK components + run: | + $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;29.0.3' + - name: Accept licenses + run: yes | flutter doctor --android-licenses + + - name: Clean flutter + run: flutter clean + + - name: Install deps + run: flutter pub get + + - name: Build Android APK + run: flutter build apk --release --build-number=${{ github.run_number }} + + - name: Sign APK + uses: r0adkll/sign-android-release@v1 + with: + releaseDirectory: client/build/app/outputs/flutter-apk + signingKeyBase64: "${{ secrets.ANDROID_SIGNING_KEY_BASE64 }}" + alias: "${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}" + keyStorePassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" + keyPassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" + + - name: Upload Android Artifact + uses: actions/upload-artifact@v4 + if: startsWith(github.ref, 'refs/tags/') + with: + name: android-app-apk + path: "client/build/app/outputs/flutter-apk/app-release.apk" + retention-days: 2 + release: - needs: [build-ios, build-android] + needs: [build-ios, build-android, build-android-apk] # Create release only if CI was triggered by a tag. if: startsWith(github.ref, 'refs/tags/') uses: ./.github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 410f7943..7d41668f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -28,6 +28,7 @@ jobs: files: | ./artifacts/Defguard.ipa ./artifacts/app-release.aab + ./artifacts/app-release.apk create-sbom: needs: [create-release] From c01da526e7bfb48890d3929a930ec348f2703eb8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 21 Nov 2025 09:58:14 +0100 Subject: [PATCH 02/65] Implement "force all traffic" enterprise setting (#159) Related issue: https://github.com/DefGuard/defguard/issues/880 Adds "force all traffic" option to enterprise settings. When selected, all clients are forced to route all traffic via the vpn. --- client/lib/data/db/database.dart | 5 +- client/lib/data/db/database.g.dart | 145 ++++++++++-------- client/lib/data/db/enums.dart | 31 ++++ client/lib/data/proxy/enrollment.dart | 13 +- client/lib/data/proxy/enrollment.g.dart | 14 ++ client/lib/enterprise/config_update.dart | 3 +- .../screens/name_device_screen.dart | 2 +- .../screens/instance/instance_screen.dart | 27 ++-- .../instance/services/tunnel_service.dart | 6 +- .../widgets/routing_method_dialog.dart | 2 + flake.lock | 6 +- 11 files changed, 164 insertions(+), 90 deletions(-) diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 5c34f333..4e1a9b39 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -28,8 +28,9 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey { TextColumn get poolingToken => text()(); - @JsonKey('disable_all_traffic') - BoolColumn get disableAllTraffic => boolean()(); + @JsonKey('client_traffic_policy') + IntColumn get clientTrafficPolicy => + integer().map(const ClientTrafficPolicyConverter())(); @JsonKey('enterprise_enabled') BoolColumn get enterpriseEnabled => boolean()(); diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index 2fca15c6..febbb7a5 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -93,20 +93,18 @@ class $DefguardInstancesTable extends DefguardInstances type: DriftSqlType.string, requiredDuringInsert: true, ); - static const VerificationMeta _disableAllTrafficMeta = const VerificationMeta( - 'disableAllTraffic', - ); @override - late final GeneratedColumn disableAllTraffic = GeneratedColumn( - 'disable_all_traffic', - aliasedName, - false, - type: DriftSqlType.bool, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("disable_all_traffic" IN (0, 1))', - ), - ); + late final GeneratedColumnWithTypeConverter + clientTrafficPolicy = + GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + $DefguardInstancesTable.$converterclientTrafficPolicy, + ); static const VerificationMeta _enterpriseEnabledMeta = const VerificationMeta( 'enterpriseEnabled', ); @@ -165,7 +163,7 @@ class $DefguardInstancesTable extends DefguardInstances proxyUrl, username, poolingToken, - disableAllTraffic, + clientTrafficPolicy, enterpriseEnabled, pubKey, privateKey, @@ -245,17 +243,6 @@ class $DefguardInstancesTable extends DefguardInstances } else if (isInserting) { context.missing(_poolingTokenMeta); } - if (data.containsKey('disable_all_traffic')) { - context.handle( - _disableAllTrafficMeta, - disableAllTraffic.isAcceptableOrUnknown( - data['disable_all_traffic']!, - _disableAllTrafficMeta, - ), - ); - } else if (isInserting) { - context.missing(_disableAllTrafficMeta); - } if (data.containsKey('enterprise_enabled')) { context.handle( _enterpriseEnabledMeta, @@ -335,10 +322,13 @@ class $DefguardInstancesTable extends DefguardInstances DriftSqlType.string, data['${effectivePrefix}pooling_token'], )!, - disableAllTraffic: attachedDatabase.typeMapping.read( - DriftSqlType.bool, - data['${effectivePrefix}disable_all_traffic'], - )!, + clientTrafficPolicy: $DefguardInstancesTable.$converterclientTrafficPolicy + .fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + ), enterpriseEnabled: attachedDatabase.typeMapping.read( DriftSqlType.bool, data['${effectivePrefix}enterprise_enabled'], @@ -362,6 +352,9 @@ class $DefguardInstancesTable extends DefguardInstances $DefguardInstancesTable createAlias(String alias) { return $DefguardInstancesTable(attachedDatabase, alias); } + + static TypeConverter $converterclientTrafficPolicy = + const ClientTrafficPolicyConverter(); } class DefguardInstance extends DataClass @@ -374,7 +367,7 @@ class DefguardInstance extends DataClass final String proxyUrl; final String username; final String poolingToken; - final bool disableAllTraffic; + final ClientTrafficPolicy clientTrafficPolicy; final bool enterpriseEnabled; final String pubKey; final String privateKey; @@ -388,7 +381,7 @@ class DefguardInstance extends DataClass required this.proxyUrl, required this.username, required this.poolingToken, - required this.disableAllTraffic, + required this.clientTrafficPolicy, required this.enterpriseEnabled, required this.pubKey, required this.privateKey, @@ -405,7 +398,13 @@ class DefguardInstance extends DataClass map['proxy_url'] = Variable(proxyUrl); map['username'] = Variable(username); map['pooling_token'] = Variable(poolingToken); - map['disable_all_traffic'] = Variable(disableAllTraffic); + { + map['client_traffic_policy'] = Variable( + $DefguardInstancesTable.$converterclientTrafficPolicy.toSql( + clientTrafficPolicy, + ), + ); + } map['enterprise_enabled'] = Variable(enterpriseEnabled); map['pub_key'] = Variable(pubKey); map['private_key'] = Variable(privateKey); @@ -423,7 +422,7 @@ class DefguardInstance extends DataClass proxyUrl: Value(proxyUrl), username: Value(username), poolingToken: Value(poolingToken), - disableAllTraffic: Value(disableAllTraffic), + clientTrafficPolicy: Value(clientTrafficPolicy), enterpriseEnabled: Value(enterpriseEnabled), pubKey: Value(pubKey), privateKey: Value(privateKey), @@ -445,7 +444,9 @@ class DefguardInstance extends DataClass proxyUrl: serializer.fromJson(json['proxy_url']), username: serializer.fromJson(json['username']), poolingToken: serializer.fromJson(json['poolingToken']), - disableAllTraffic: serializer.fromJson(json['disable_all_traffic']), + clientTrafficPolicy: serializer.fromJson( + json['client_traffic_policy'], + ), enterpriseEnabled: serializer.fromJson(json['enterprise_enabled']), pubKey: serializer.fromJson(json['pubKey']), privateKey: serializer.fromJson(json['privateKey']), @@ -464,7 +465,9 @@ class DefguardInstance extends DataClass 'proxy_url': serializer.toJson(proxyUrl), 'username': serializer.toJson(username), 'poolingToken': serializer.toJson(poolingToken), - 'disable_all_traffic': serializer.toJson(disableAllTraffic), + 'client_traffic_policy': serializer.toJson( + clientTrafficPolicy, + ), 'enterprise_enabled': serializer.toJson(enterpriseEnabled), 'pubKey': serializer.toJson(pubKey), 'privateKey': serializer.toJson(privateKey), @@ -481,7 +484,7 @@ class DefguardInstance extends DataClass String? proxyUrl, String? username, String? poolingToken, - bool? disableAllTraffic, + ClientTrafficPolicy? clientTrafficPolicy, bool? enterpriseEnabled, String? pubKey, String? privateKey, @@ -495,7 +498,7 @@ class DefguardInstance extends DataClass proxyUrl: proxyUrl ?? this.proxyUrl, username: username ?? this.username, poolingToken: poolingToken ?? this.poolingToken, - disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, @@ -513,9 +516,9 @@ class DefguardInstance extends DataClass poolingToken: data.poolingToken.present ? data.poolingToken.value : this.poolingToken, - disableAllTraffic: data.disableAllTraffic.present - ? data.disableAllTraffic.value - : this.disableAllTraffic, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, enterpriseEnabled: data.enterpriseEnabled.present ? data.enterpriseEnabled.value : this.enterpriseEnabled, @@ -540,7 +543,7 @@ class DefguardInstance extends DataClass ..write('proxyUrl: $proxyUrl, ') ..write('username: $username, ') ..write('poolingToken: $poolingToken, ') - ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') @@ -559,7 +562,7 @@ class DefguardInstance extends DataClass proxyUrl, username, poolingToken, - disableAllTraffic, + clientTrafficPolicy, enterpriseEnabled, pubKey, privateKey, @@ -577,7 +580,7 @@ class DefguardInstance extends DataClass other.proxyUrl == this.proxyUrl && other.username == this.username && other.poolingToken == this.poolingToken && - other.disableAllTraffic == this.disableAllTraffic && + other.clientTrafficPolicy == this.clientTrafficPolicy && other.enterpriseEnabled == this.enterpriseEnabled && other.pubKey == this.pubKey && other.privateKey == this.privateKey && @@ -593,7 +596,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { final Value proxyUrl; final Value username; final Value poolingToken; - final Value disableAllTraffic; + final Value clientTrafficPolicy; final Value enterpriseEnabled; final Value pubKey; final Value privateKey; @@ -607,7 +610,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { this.proxyUrl = const Value.absent(), this.username = const Value.absent(), this.poolingToken = const Value.absent(), - this.disableAllTraffic = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), this.enterpriseEnabled = const Value.absent(), this.pubKey = const Value.absent(), this.privateKey = const Value.absent(), @@ -622,7 +625,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { required String proxyUrl, required String username, required String poolingToken, - required bool disableAllTraffic, + required ClientTrafficPolicy clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -634,7 +637,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { proxyUrl = Value(proxyUrl), username = Value(username), poolingToken = Value(poolingToken), - disableAllTraffic = Value(disableAllTraffic), + clientTrafficPolicy = Value(clientTrafficPolicy), enterpriseEnabled = Value(enterpriseEnabled), pubKey = Value(pubKey), privateKey = Value(privateKey), @@ -648,7 +651,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Expression? proxyUrl, Expression? username, Expression? poolingToken, - Expression? disableAllTraffic, + Expression? clientTrafficPolicy, Expression? enterpriseEnabled, Expression? pubKey, Expression? privateKey, @@ -663,7 +666,8 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (proxyUrl != null) 'proxy_url': proxyUrl, if (username != null) 'username': username, if (poolingToken != null) 'pooling_token': poolingToken, - if (disableAllTraffic != null) 'disable_all_traffic': disableAllTraffic, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, if (pubKey != null) 'pub_key': pubKey, if (privateKey != null) 'private_key': privateKey, @@ -680,7 +684,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Value? proxyUrl, Value? username, Value? poolingToken, - Value? disableAllTraffic, + Value? clientTrafficPolicy, Value? enterpriseEnabled, Value? pubKey, Value? privateKey, @@ -695,7 +699,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { proxyUrl: proxyUrl ?? this.proxyUrl, username: username ?? this.username, poolingToken: poolingToken ?? this.poolingToken, - disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, @@ -730,8 +734,12 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (poolingToken.present) { map['pooling_token'] = Variable(poolingToken.value); } - if (disableAllTraffic.present) { - map['disable_all_traffic'] = Variable(disableAllTraffic.value); + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable( + $DefguardInstancesTable.$converterclientTrafficPolicy.toSql( + clientTrafficPolicy.value, + ), + ); } if (enterpriseEnabled.present) { map['enterprise_enabled'] = Variable(enterpriseEnabled.value); @@ -759,7 +767,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { ..write('proxyUrl: $proxyUrl, ') ..write('username: $username, ') ..write('poolingToken: $poolingToken, ') - ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') @@ -1632,7 +1640,7 @@ typedef $$DefguardInstancesTableCreateCompanionBuilder = required String proxyUrl, required String username, required String poolingToken, - required bool disableAllTraffic, + required ClientTrafficPolicy clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -1648,7 +1656,7 @@ typedef $$DefguardInstancesTableUpdateCompanionBuilder = Value proxyUrl, Value username, Value poolingToken, - Value disableAllTraffic, + Value clientTrafficPolicy, Value enterpriseEnabled, Value pubKey, Value privateKey, @@ -1739,9 +1747,10 @@ class $$DefguardInstancesTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters get disableAllTraffic => $composableBuilder( - column: $table.disableAllTraffic, - builder: (column) => ColumnFilters(column), + ColumnWithTypeConverterFilters + get clientTrafficPolicy => $composableBuilder( + column: $table.clientTrafficPolicy, + builder: (column) => ColumnWithTypeConverterFilters(column), ); ColumnFilters get enterpriseEnabled => $composableBuilder( @@ -1839,8 +1848,8 @@ class $$DefguardInstancesTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings get disableAllTraffic => $composableBuilder( - column: $table.disableAllTraffic, + ColumnOrderings get clientTrafficPolicy => $composableBuilder( + column: $table.clientTrafficPolicy, builder: (column) => ColumnOrderings(column), ); @@ -1900,8 +1909,9 @@ class $$DefguardInstancesTableAnnotationComposer builder: (column) => column, ); - GeneratedColumn get disableAllTraffic => $composableBuilder( - column: $table.disableAllTraffic, + GeneratedColumnWithTypeConverter + get clientTrafficPolicy => $composableBuilder( + column: $table.clientTrafficPolicy, builder: (column) => column, ); @@ -1990,7 +2000,8 @@ class $$DefguardInstancesTableTableManager Value proxyUrl = const Value.absent(), Value username = const Value.absent(), Value poolingToken = const Value.absent(), - Value disableAllTraffic = const Value.absent(), + Value clientTrafficPolicy = + const Value.absent(), Value enterpriseEnabled = const Value.absent(), Value pubKey = const Value.absent(), Value privateKey = const Value.absent(), @@ -2004,7 +2015,7 @@ class $$DefguardInstancesTableTableManager proxyUrl: proxyUrl, username: username, poolingToken: poolingToken, - disableAllTraffic: disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled, pubKey: pubKey, privateKey: privateKey, @@ -2020,7 +2031,7 @@ class $$DefguardInstancesTableTableManager required String proxyUrl, required String username, required String poolingToken, - required bool disableAllTraffic, + required ClientTrafficPolicy clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -2034,7 +2045,7 @@ class $$DefguardInstancesTableTableManager proxyUrl: proxyUrl, username: username, poolingToken: poolingToken, - disableAllTraffic: disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled, pubKey: pubKey, privateKey: privateKey, diff --git a/client/lib/data/db/enums.dart b/client/lib/data/db/enums.dart index 021b0767..36b4c072 100644 --- a/client/lib/data/db/enums.dart +++ b/client/lib/data/db/enums.dart @@ -83,3 +83,34 @@ class LocationMfaModeConverter extends TypeConverter { return value.value; } } + +@j.JsonEnum() +enum ClientTrafficPolicy { + @j.JsonValue(0) + none(0), + @j.JsonValue(1) + disableAllTraffic(1), + @j.JsonValue(2) + forceAllTraffic(2); + + final int value; + + const ClientTrafficPolicy(this.value); + + static ClientTrafficPolicy fromValue(int value) => + ClientTrafficPolicy.values.firstWhere((e) => e.value == value); +} + +class ClientTrafficPolicyConverter extends TypeConverter { + const ClientTrafficPolicyConverter(); + + @override + ClientTrafficPolicy fromSql(int fromDb) { + return ClientTrafficPolicy.fromValue(fromDb); + } + + @override + int toSql(ClientTrafficPolicy value) { + return value.value; + } +} diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index cda111ff..0c811719 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -252,6 +252,7 @@ class InstanceInfo { final String username; final bool enterpriseEnabled; final bool disableAllTraffic; + final ClientTrafficPolicy? clientTrafficPolicy; const InstanceInfo({ required this.id, @@ -261,6 +262,7 @@ class InstanceInfo { required this.username, required this.enterpriseEnabled, required this.disableAllTraffic, + required this.clientTrafficPolicy, }); factory InstanceInfo.fromJson(Map json) => @@ -275,7 +277,7 @@ class InstanceInfo { proxyUrl == other.proxyUrl && username == other.username && enterpriseEnabled == other.enterpriseEnabled && - disableAllTraffic == other.disableAllTraffic; + getPolicy() == other.clientTrafficPolicy; } DefguardInstancesCompanion toCompanion({DefguardInstance? instance}) { @@ -290,10 +292,17 @@ class InstanceInfo { proxyUrl: d.Value(proxyUrl), username: d.Value(username), enterpriseEnabled: d.Value(enterpriseEnabled), - disableAllTraffic: d.Value(disableAllTraffic), + clientTrafficPolicy: d.Value(getPolicy()), uuid: d.Value(id), ); } + + /// Retrieves `ClientTrafficPolicy` while ensuring backwards compatibility + ClientTrafficPolicy getPolicy() { + return clientTrafficPolicy ?? (disableAllTraffic + ? ClientTrafficPolicy.disableAllTraffic + : ClientTrafficPolicy.none); + } } @JsonSerializable() diff --git a/client/lib/data/proxy/enrollment.g.dart b/client/lib/data/proxy/enrollment.g.dart index 8d832881..df153cef 100644 --- a/client/lib/data/proxy/enrollment.g.dart +++ b/client/lib/data/proxy/enrollment.g.dart @@ -389,6 +389,10 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'disable_all_traffic', (v) => v as bool, ), + clientTrafficPolicy: $checkedConvert( + 'client_traffic_policy', + (v) => $enumDecodeNullable(_$ClientTrafficPolicyEnumMap, v), + ), ); return val; }, @@ -396,6 +400,7 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'proxyUrl': 'proxy_url', 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', + 'clientTrafficPolicy': 'client_traffic_policy', }, ); @@ -407,6 +412,7 @@ const _$InstanceInfoFieldMap = { 'username': 'username', 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', + 'clientTrafficPolicy': 'client_traffic_policy', }; Map _$InstanceInfoToJson(InstanceInfo instance) => @@ -418,8 +424,16 @@ Map _$InstanceInfoToJson(InstanceInfo instance) => 'username': instance.username, 'enterprise_enabled': instance.enterpriseEnabled, 'disable_all_traffic': instance.disableAllTraffic, + 'client_traffic_policy': + _$ClientTrafficPolicyEnumMap[instance.clientTrafficPolicy], }; +const _$ClientTrafficPolicyEnumMap = { + ClientTrafficPolicy.none: 0, + ClientTrafficPolicy.disableAllTraffic: 1, + ClientTrafficPolicy.forceAllTraffic: 2, +}; + AppInfoResponse _$AppInfoResponseFromJson(Map json) => $checkedCreate('AppInfoResponse', json, ($checkedConvert) { final val = AppInfoResponse( diff --git a/client/lib/enterprise/config_update.dart b/client/lib/enterprise/config_update.dart index 91c7107d..f7778916 100644 --- a/client/lib/enterprise/config_update.dart +++ b/client/lib/enterprise/config_update.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobile/data/db/database.dart'; +import 'package:mobile/data/db/enums.dart'; import 'package:mobile/open/api.dart'; import 'package:mobile/open/widgets/toaster/toast_manager.dart'; import 'package:mobile/utils/update_instance.dart'; @@ -86,7 +87,7 @@ class ConfigurationUpdater extends HookConsumerWidget { // instance lost it's enterprise status if (responseStatus == 402) { final instanceUpdate = instance.copyWith( - disableAllTraffic: false, + clientTrafficPolicy: ClientTrafficPolicy.none, enterpriseEnabled: false, ); await db.managers.defguardInstances.replace(instanceUpdate); diff --git a/client/lib/open/screens/add_instance/screens/name_device_screen.dart b/client/lib/open/screens/add_instance/screens/name_device_screen.dart index a3ce5cea..c80a626d 100644 --- a/client/lib/open/screens/add_instance/screens/name_device_screen.dart +++ b/client/lib/open/screens/add_instance/screens/name_device_screen.dart @@ -58,7 +58,7 @@ class NameDeviceScreen extends HookConsumerWidget { uuid: createResponse.instance.id, deviceId: createResponse.device.id, enterpriseEnabled: createResponse.instance.enterpriseEnabled, - disableAllTraffic: createResponse.instance.disableAllTraffic, + clientTrafficPolicy: createResponse.instance.getPolicy(), proxyUrl: createResponse.instance.proxyUrl, url: screenData.startResponse.instance.url, username: createResponse.instance.username, diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart index 11974629..a725ec56 100644 --- a/client/lib/open/screens/instance/instance_screen.dart +++ b/client/lib/open/screens/instance/instance_screen.dart @@ -415,19 +415,20 @@ class _LocationItem extends HookConsumerWidget { ); }, ), - if (!instance.disableAllTraffic) - DgMenuItem( - text: "Select Traffic Routing", - onTap: () { - showDialog( - context: context, - builder: (_) => RoutingMethodDialog( - location: location, - intention: RoutingMethodDialogIntention.save, - ), - ); - }, - ), + if (instance.clientTrafficPolicy == ClientTrafficPolicy.none) + DgMenuItem( + text: "Select Traffic Routing", + onTap: () { + showDialog( + context: context, + builder: (_) => RoutingMethodDialog( + location: location, + intention: RoutingMethodDialogIntention.save, + clientTrafficPolicy: instance.clientTrafficPolicy, + ), + ); + }, + ), ]; }, [location, instance]); diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index e156917c..a52a774f 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -35,9 +35,12 @@ class TunnelService { // handle traffic type selection if necessary late RoutingMethod trafficMethod; - if (instance.disableAllTraffic) { + if (instance.clientTrafficPolicy == ClientTrafficPolicy.disableAllTraffic) { // instance enforces predefined traffic trafficMethod = RoutingMethod.predefined; + } else if (instance.clientTrafficPolicy == ClientTrafficPolicy.forceAllTraffic) { + // instance enforces all traffic + trafficMethod = RoutingMethod.all; } else { // instance allows traffic type selection - use stored method or display selection dialog if (location.trafficMethod != null) { @@ -52,6 +55,7 @@ class TunnelService { builder: (_) => RoutingMethodDialog( location: location, intention: dialogIntention, + clientTrafficPolicy: instance.clientTrafficPolicy, ), ); // smth went wrong or user canceled the operation diff --git a/client/lib/open/screens/instance/widgets/routing_method_dialog.dart b/client/lib/open/screens/instance/widgets/routing_method_dialog.dart index d2cc29b5..e64736e3 100644 --- a/client/lib/open/screens/instance/widgets/routing_method_dialog.dart +++ b/client/lib/open/screens/instance/widgets/routing_method_dialog.dart @@ -24,12 +24,14 @@ enum RoutingMethodDialogIntention { connect, save, next } class RoutingMethodDialog extends HookConsumerWidget { final Location location; + final ClientTrafficPolicy clientTrafficPolicy; final RoutingMethodDialogIntention intention; const RoutingMethodDialog({ super.key, required this.location, required this.intention, + required this.clientTrafficPolicy, }); String _getSubmitText() { diff --git a/flake.lock b/flake.lock index 5ca09a97..333e23c3 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750776420, - "narHash": "sha256-/CG+w0o0oJ5itVklOoLbdn2dGB0wbZVOoDm4np6w09A=", + "lastModified": 1763421233, + "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "30a61f056ac492e3b7cdcb69c1e6abdcf00e39cf", + "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", "type": "github" }, "original": { From cbefcbdefca70472e6af886f189ee22cf1faf6d9 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 24 Nov 2025 07:08:39 +0100 Subject: [PATCH 03/65] Add DB migrations (#160) --- client/README.md | 28 +- client/build.yaml | 4 + .../defguard/drift_schema_v1.json | 1 + .../defguard/drift_schema_v2.json | 1 + client/lib/data/db/database.dart | 26 +- client/lib/data/db/database.g.dart | 11 +- client/lib/data/db/database.steps.dart | 335 +++++ client/lib/data/proxy/enrollment.dart | 2 + .../screens/name_device_screen.dart | 2 +- client/pubspec.lock | 16 +- .../test/drift/defguard/generated/schema.dart | 23 + .../drift/defguard/generated/schema_v1.dart | 1275 +++++++++++++++++ .../drift/defguard/generated/schema_v2.dart | 1275 +++++++++++++++++ .../test/drift/defguard/migration_test.dart | 79 + flake.nix | 3 +- 15 files changed, 3059 insertions(+), 22 deletions(-) create mode 100644 client/drift_schemas/defguard/drift_schema_v1.json create mode 100644 client/drift_schemas/defguard/drift_schema_v2.json create mode 100644 client/lib/data/db/database.steps.dart create mode 100644 client/test/drift/defguard/generated/schema.dart create mode 100644 client/test/drift/defguard/generated/schema_v1.dart create mode 100644 client/test/drift/defguard/generated/schema_v2.dart create mode 100644 client/test/drift/defguard/migration_test.dart diff --git a/client/README.md b/client/README.md index edc1edfa..380db3c9 100644 --- a/client/README.md +++ b/client/README.md @@ -1,10 +1,8 @@ -# mobile_client - -Defguard mobile client +# Defguard mobile client ## Getting Started -This project is a starting point for a Flutter application. +This is a Flutter application. A few resources to get you started if this is your first Flutter project: @@ -14,3 +12,25 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## Database and migrations + +We use [drift](https://drift.simonbinder.eu/) persistence library with [SQLite](https://sqlite.org/index.html) +database. The model is defined in `lib/data/db/database.dart`. + +When changing the schema: + +1. Make changes to the model in `database.dart` file. +2. Bump schema version in `class AppDatabase`. +3. Generate migrations and tests: + +```bash +flutter pub run drift_dev make-migrations +``` + +4. Add your migration step to `AppDatabase` `onUpgrade`. +5. Run the tests: + +```bash +flutter test +``` diff --git a/client/build.yaml b/client/build.yaml index bcfab5a4..7fc26877 100644 --- a/client/build.yaml +++ b/client/build.yaml @@ -9,3 +9,7 @@ targets: create_factory: true create_to_json: true create_field_map: true + drift_dev: + options: + databases: + defguard: lib/data/db/database.dart diff --git a/client/drift_schemas/defguard/drift_schema_v1.json b/client/drift_schemas/defguard/drift_schema_v1.json new file mode 100644 index 00000000..df952fbf --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v1.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"disable_all_traffic","getter_name":"disableAllTraffic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"disable_all_traffic\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"disable_all_traffic\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/drift_schemas/defguard/drift_schema_v2.json b/client/drift_schemas/defguard/drift_schema_v2.json new file mode 100644 index 00000000..2a2bb13f --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v2.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 4e1a9b39..363f784c 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -1,6 +1,7 @@ import "package:drift/drift.dart"; import "package:drift_flutter/drift_flutter.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:mobile/data/db/database.steps.dart"; import "package:mobile/data/db/enums.dart"; import "package:path_provider/path_provider.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; @@ -29,8 +30,9 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey { TextColumn get poolingToken => text()(); @JsonKey('client_traffic_policy') - IntColumn get clientTrafficPolicy => - integer().map(const ClientTrafficPolicyConverter())(); + IntColumn get clientTrafficPolicy => integer() + .withDefault(const Constant(0)) + .map(const ClientTrafficPolicyConverter())(); @JsonKey('enterprise_enabled') BoolColumn get enterpriseEnabled => boolean()(); @@ -96,7 +98,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 1; + int get schemaVersion => 2; @override MigrationStrategy get migration { @@ -104,6 +106,24 @@ class AppDatabase extends _$AppDatabase { beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); }, + onUpgrade: stepByStep( + from1To2: (m, schema) async { + // 1. Add the new column manually. + // This ensures Drift doesn't trigger a "Recreate Table" that might + // drop 'disable_all_traffic' before we are done with it. + await customStatement( + 'ALTER TABLE defguard_instances ADD COLUMN client_traffic_policy INTEGER NOT NULL DEFAULT 0', + ); + // 2. Update values derived from the old column + await customStatement(''' + UPDATE defguard_instances + SET client_traffic_policy = + CASE WHEN disable_all_traffic = 1 THEN 1 ELSE 0 END; + '''); + // 3. Drop old "disable_all_traffic" column + await m.dropColumn(defguardInstances, "disable_all_traffic"); + }, + ), ); } diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index febbb7a5..2c951d09 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -101,7 +101,8 @@ class $DefguardInstancesTable extends DefguardInstances aliasedName, false, type: DriftSqlType.int, - requiredDuringInsert: true, + requiredDuringInsert: false, + defaultValue: const Constant(0), ).withConverter( $DefguardInstancesTable.$converterclientTrafficPolicy, ); @@ -625,7 +626,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { required String proxyUrl, required String username, required String poolingToken, - required ClientTrafficPolicy clientTrafficPolicy, + this.clientTrafficPolicy = const Value.absent(), required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -637,7 +638,6 @@ class DefguardInstancesCompanion extends UpdateCompanion { proxyUrl = Value(proxyUrl), username = Value(username), poolingToken = Value(poolingToken), - clientTrafficPolicy = Value(clientTrafficPolicy), enterpriseEnabled = Value(enterpriseEnabled), pubKey = Value(pubKey), privateKey = Value(privateKey), @@ -1640,7 +1640,7 @@ typedef $$DefguardInstancesTableCreateCompanionBuilder = required String proxyUrl, required String username, required String poolingToken, - required ClientTrafficPolicy clientTrafficPolicy, + Value clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -2031,7 +2031,8 @@ class $$DefguardInstancesTableTableManager required String proxyUrl, required String username, required String poolingToken, - required ClientTrafficPolicy clientTrafficPolicy, + Value clientTrafficPolicy = + const Value.absent(), required bool enterpriseEnabled, required String pubKey, required String privateKey, diff --git a/client/lib/data/db/database.steps.dart b/client/lib/data/db/database.steps.dart new file mode 100644 index 00000000..c2c82037 --- /dev/null +++ b/client/lib/data/db/database.steps.dart @@ -0,0 +1,335 @@ +// dart format width=80 +import 'package:drift/internal/versioned_schema.dart' as i0; +import 'package:drift/drift.dart' as i1; +import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import + +// GENERATED BY drift_dev, DO NOT MODIFY. +final class Schema2 extends i0.VersionedSchema { + Schema2({required super.database}) : super(version: 2); + @override + late final List entities = [ + defguardInstances, + locations, + ]; + late final Shape0 defguardInstances = Shape0( + source: i0.VersionedTable( + entityName: 'defguard_instances', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 locations = Shape1( + source: i0.VersionedTable( + entityName: 'locations', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_13, + _column_14, + _column_1, + _column_15, + _column_10, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape0 extends i0.VersionedTable { + Shape0({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get uuid => + columnsByName['uuid']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => + columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get deviceId => + columnsByName['device_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get proxyUrl => + columnsByName['proxy_url']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => + columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get poolingToken => + columnsByName['pooling_token']! as i1.GeneratedColumn; + i1.GeneratedColumn get clientTrafficPolicy => + columnsByName['client_traffic_policy']! as i1.GeneratedColumn; + i1.GeneratedColumn get enterpriseEnabled => + columnsByName['enterprise_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get privateKey => + columnsByName['private_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaKeysStored => + columnsByName['mfa_keys_stored']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_0(String aliasedName) => + i1.GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: i1.DriftSqlType.int, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); +i1.GeneratedColumn _column_1(String aliasedName) => + i1.GeneratedColumn( + 'name', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_2(String aliasedName) => + i1.GeneratedColumn( + 'uuid', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_3(String aliasedName) => + i1.GeneratedColumn( + 'url', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_4(String aliasedName) => + i1.GeneratedColumn( + 'device_id', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_5(String aliasedName) => + i1.GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_6(String aliasedName) => + i1.GeneratedColumn( + 'username', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_7(String aliasedName) => + i1.GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_8(String aliasedName) => + i1.GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_9(String aliasedName) => + i1.GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); +i1.GeneratedColumn _column_10(String aliasedName) => + i1.GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_11(String aliasedName) => + i1.GeneratedColumn( + 'private_key', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_12(String aliasedName) => + i1.GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + +class Shape1 extends i0.VersionedTable { + Shape1({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => + columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get networkId => + columnsByName['network_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get address => + columnsByName['address']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get endpoint => + columnsByName['endpoint']! as i1.GeneratedColumn; + i1.GeneratedColumn get allowedIps => + columnsByName['allowed_ips']! as i1.GeneratedColumn; + i1.GeneratedColumn get dns => + columnsByName['dns']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaEnabled => + columnsByName['mfa_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get trafficMethod => + columnsByName['traffic_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaMethod => + columnsByName['mfa_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get keepAliveInterval => + columnsByName['keep_alive_interval']! as i1.GeneratedColumn; + i1.GeneratedColumn get locationMfaMode => + columnsByName['location_mfa_mode']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_13(String aliasedName) => + i1.GeneratedColumn( + 'instance', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_14(String aliasedName) => + i1.GeneratedColumn( + 'network_id', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_15(String aliasedName) => + i1.GeneratedColumn( + 'address', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_16(String aliasedName) => + i1.GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_17(String aliasedName) => + i1.GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_18(String aliasedName) => + i1.GeneratedColumn( + 'dns', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_19(String aliasedName) => + i1.GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); +i1.GeneratedColumn _column_20(String aliasedName) => + i1.GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_21(String aliasedName) => + i1.GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_22(String aliasedName) => + i1.GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_23(String aliasedName) => + i1.GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i0.MigrationStepWithVersion migrationSteps({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, +}) { + return (currentVersion, database) async { + switch (currentVersion) { + case 1: + final schema = Schema2(database: database); + final migrator = i1.Migrator(database, schema); + await from1To2(migrator, schema); + return 2; + default: + throw ArgumentError.value('Unknown migration from $currentVersion'); + } + }; +} + +i1.OnUpgrade stepByStep({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, +}) => i0.VersionedSchema.stepByStepHelper( + step: migrationSteps(from1To2: from1To2), +); diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index 0c811719..916ca791 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -261,6 +261,8 @@ class InstanceInfo { required this.proxyUrl, required this.username, required this.enterpriseEnabled, + // deprecated, use clientTrafficPolicy instead + @Deprecated('1.6') required this.disableAllTraffic, required this.clientTrafficPolicy, }); diff --git a/client/lib/open/screens/add_instance/screens/name_device_screen.dart b/client/lib/open/screens/add_instance/screens/name_device_screen.dart index c80a626d..ba583b7a 100644 --- a/client/lib/open/screens/add_instance/screens/name_device_screen.dart +++ b/client/lib/open/screens/add_instance/screens/name_device_screen.dart @@ -58,7 +58,7 @@ class NameDeviceScreen extends HookConsumerWidget { uuid: createResponse.instance.id, deviceId: createResponse.device.id, enterpriseEnabled: createResponse.instance.enterpriseEnabled, - clientTrafficPolicy: createResponse.instance.getPolicy(), + clientTrafficPolicy: drift.Value(createResponse.instance.getPolicy()), proxyUrl: createResponse.instance.proxyUrl, url: screenData.startResponse.instance.url, username: createResponse.instance.username, diff --git a/client/pubspec.lock b/client/pubspec.lock index 3555290a..c1b1ac58 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -884,10 +884,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1425,26 +1425,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" timezone: dependency: transitive description: diff --git a/client/test/drift/defguard/generated/schema.dart b/client/test/drift/defguard/generated/schema.dart new file mode 100644 index 00000000..b2b7404b --- /dev/null +++ b/client/test/drift/defguard/generated/schema.dart @@ -0,0 +1,23 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; +import 'package:drift/internal/migrations.dart'; +import 'schema_v1.dart' as v1; +import 'schema_v2.dart' as v2; + +class GeneratedHelper implements SchemaInstantiationHelper { + @override + GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { + switch (version) { + case 1: + return v1.DatabaseAtV1(db); + case 2: + return v2.DatabaseAtV2(db); + default: + throw MissingSchemaException(version, versions); + } + } + + static const versions = const [1, 2]; +} diff --git a/client/test/drift/defguard/generated/schema_v1.dart b/client/test/drift/defguard/generated/schema_v1.dart new file mode 100644 index 00000000..99d607bf --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v1.dart @@ -0,0 +1,1275 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn disableAllTraffic = GeneratedColumn( + 'disable_all_traffic', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("disable_all_traffic" IN (0, 1))', + ), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + disableAllTraffic, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + disableAllTraffic: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}disable_all_traffic'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final bool disableAllTraffic; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.disableAllTraffic, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['disable_all_traffic'] = Variable(disableAllTraffic); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + disableAllTraffic: Value(disableAllTraffic), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + disableAllTraffic: serializer.fromJson(json['disableAllTraffic']), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'disableAllTraffic': serializer.toJson(disableAllTraffic), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + bool? disableAllTraffic, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + disableAllTraffic: data.disableAllTraffic.present + ? data.disableAllTraffic.value + : this.disableAllTraffic, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + disableAllTraffic, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.disableAllTraffic == this.disableAllTraffic && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value disableAllTraffic; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.disableAllTraffic = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + required bool disableAllTraffic, + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + disableAllTraffic = Value(disableAllTraffic), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? disableAllTraffic, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (disableAllTraffic != null) 'disable_all_traffic': disableAllTraffic, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? disableAllTraffic, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (disableAllTraffic.present) { + map['disable_all_traffic'] = Variable(disableAllTraffic.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV1 extends GeneratedDatabase { + DatabaseAtV1(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 1; +} diff --git a/client/test/drift/defguard/generated/schema_v2.dart b/client/test/drift/defguard/generated/schema_v2.dart new file mode 100644 index 00000000..7419234a --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v2.dart @@ -0,0 +1,1275 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn clientTrafficPolicy = GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + clientTrafficPolicy: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final int clientTrafficPolicy; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.clientTrafficPolicy, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['client_traffic_policy'] = Variable(clientTrafficPolicy); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + clientTrafficPolicy: Value(clientTrafficPolicy), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + clientTrafficPolicy: serializer.fromJson( + json['clientTrafficPolicy'], + ), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'clientTrafficPolicy': serializer.toJson(clientTrafficPolicy), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + int? clientTrafficPolicy, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.clientTrafficPolicy == this.clientTrafficPolicy && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value clientTrafficPolicy; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + this.clientTrafficPolicy = const Value.absent(), + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? clientTrafficPolicy, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? clientTrafficPolicy, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable(clientTrafficPolicy.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV2 extends GeneratedDatabase { + DatabaseAtV2(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 2; +} diff --git a/client/test/drift/defguard/migration_test.dart b/client/test/drift/defguard/migration_test.dart new file mode 100644 index 00000000..6ab73b27 --- /dev/null +++ b/client/test/drift/defguard/migration_test.dart @@ -0,0 +1,79 @@ +// dart format width=80 +// ignore_for_file: unused_local_variable, unused_import +import 'package:drift/drift.dart'; +import 'package:drift_dev/api/migrations_native.dart'; +import 'package:mobile/data/db/database.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'generated/schema.dart'; + +import 'generated/schema_v1.dart' as v1; +import 'generated/schema_v2.dart' as v2; + +void main() { + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + late SchemaVerifier verifier; + + setUpAll(() { + verifier = SchemaVerifier(GeneratedHelper()); + }); + + group('simple database migrations', () { + // These simple tests verify all possible schema updates with a simple (no + // data) migration. This is a quick way to ensure that written database + // migrations properly alter the schema. + const versions = GeneratedHelper.versions; + for (final (i, fromVersion) in versions.indexed) { + group('from $fromVersion', () { + for (final toVersion in versions.skip(i + 1)) { + test('to $toVersion', () async { + final schema = await verifier.schemaAt(fromVersion); + final db = AppDatabase(schema.newConnection()); + await verifier.migrateAndValidate(db, toVersion); + await db.close(); + }); + } + }); + } + }); + + // The following template shows how to write tests ensuring your migrations + // preserve existing data. + // Testing this can be useful for migrations that change existing columns + // (e.g. by alterating their type or constraints). Migrations that only add + // tables or columns typically don't need these advanced tests. For more + // information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity + // TODO: This generated template shows how these tests could be written. Adopt + // it to your own needs when testing migrations with data integrity. + test('migration from v1 to v2 does not corrupt data', () async { + // Add data to insert into the old database, and the expected rows after the + // migration. + // TODO: Fill these lists + final oldDefguardInstancesData = []; + final expectedNewDefguardInstancesData = []; + + final oldLocationsData = []; + final expectedNewLocationsData = []; + + await verifier.testWithDataIntegrity( + oldVersion: 1, + newVersion: 2, + createOld: v1.DatabaseAtV1.new, + createNew: v2.DatabaseAtV2.new, + openTestedDatabase: AppDatabase.new, + createItems: (batch, oldDb) { + batch.insertAll(oldDb.defguardInstances, oldDefguardInstancesData); + batch.insertAll(oldDb.locations, oldLocationsData); + }, + validateItems: (newDb) async { + expect( + expectedNewDefguardInstancesData, + await newDb.select(newDb.defguardInstances).get(), + ); + expect( + expectedNewLocationsData, + await newDb.select(newDb.locations).get(), + ); + }, + ); + }); +} diff --git a/flake.nix b/flake.nix index 9d348752..002f76ab 100644 --- a/flake.nix +++ b/flake.nix @@ -68,7 +68,7 @@ ''; in { devShell = with pkgs; - mkShell rec { + mkShell { ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk"; buildInputs = [ flutter @@ -85,6 +85,7 @@ export GDK_BACKEND=x11 export LANG=en_US.UTF-8 export QT_QPA_PLATFORM=xcb + export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath (with pkgs; [ sqlite ])}:$LD_LIBRARY_PATH"; ''; }; }); From fbf8c66b520e3a05092bd864a61188e2f5b8acb2 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:11:13 +0100 Subject: [PATCH 04/65] fix periodic SBOM regeneration (#161) * update gitignore * remove private token * update borintun submodule * another test * restore submodule version --- .github/workflows/build.yaml | 11 ++++------- .github/workflows/lint-and-test.yaml | 13 ++++++------- .github/workflows/release.yaml | 7 ------- .github/workflows/sbom-regenerate.yaml | 7 ++++--- .github/workflows/sbom.yaml | 21 +++++++++------------ .gitignore | 2 ++ 6 files changed, 25 insertions(+), 36 deletions(-) create mode 100644 .gitignore diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec1c8ff2..c7efee65 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,8 +4,8 @@ on: branches: - main - dev - - 'release/**' - - 'hotfix/**' + - "release/**" + - "hotfix/**" tags: - v*.*.* paths-ignore: @@ -24,7 +24,6 @@ jobs: uses: actions/checkout@v4 with: submodules: "recursive" - token: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} - name: Setup flutter uses: subosito/flutter-action@v2 @@ -66,7 +65,7 @@ jobs: issuer-id: ${{ secrets.API_ISSUER_ID }} api-key-id: ${{ secrets.ASC_API_KEY_ID }} api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} - + - name: Upload iOS Artifact uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') @@ -161,7 +160,7 @@ jobs: with: channel: stable flutter-version: 3.32.7 - + - name: Install Android SDK components run: | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;29.0.3' @@ -199,5 +198,3 @@ jobs: # Create release only if CI was triggered by a tag. if: startsWith(github.ref, 'refs/tags/') uses: ./.github/workflows/release.yaml - secrets: - PRIVATE_REPO_CLONING_TOKEN: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 603d5d48..01df6ba9 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -5,8 +5,8 @@ on: branches: - main - dev - - 'release/**' - - 'hotfix/**' + - "release/**" + - "hotfix/**" paths-ignore: &ignored_paths - "*.md" - "LICENSE" @@ -15,8 +15,8 @@ on: branches: - main - dev - - 'release/**' - - 'hotfix/**' + - "release/**" + - "hotfix/**" paths-ignore: *ignored_paths jobs: @@ -34,8 +34,8 @@ jobs: - name: Scan code with Trivy uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' - scan-ref: '.' + scan-type: "fs" + scan-ref: "." exit-code: "1" ignore-unfixed: true severity: "CRITICAL,HIGH,MEDIUM" @@ -66,7 +66,6 @@ jobs: # uses: actions/checkout@v4 # with: # submodules: "recursive" - # token: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} # - name: setup flutter # uses: subosito/flutter-action@v2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7d41668f..103af535 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,10 +2,6 @@ name: "Release" on: workflow_call: - secrets: - PRIVATE_REPO_CLONING_TOKEN: - description: "Cloning token" - required: true jobs: create-release: @@ -35,6 +31,3 @@ jobs: uses: ./.github/workflows/sbom.yaml with: upload_url: ${{ needs.create-release.outputs.upload_url }} - secrets: - PRIVATE_REPO_CLONING_TOKEN: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} - diff --git a/.github/workflows/sbom-regenerate.yaml b/.github/workflows/sbom-regenerate.yaml index 3bd1f03c..dff8a3c0 100644 --- a/.github/workflows/sbom-regenerate.yaml +++ b/.github/workflows/sbom-regenerate.yaml @@ -1,8 +1,10 @@ name: Periodic SBOM Regeneration +permissions: + contents: write on: schedule: - - cron: '30 2 * * *' # 2:30 AM UTC + - cron: "30 2 * * *" # 2:30 AM UTC jobs: list-releases: @@ -35,5 +37,4 @@ jobs: with: upload_url: ${{ matrix.release.uploadUrl }} tag: ${{ matrix.release.tagName }} - secrets: - PRIVATE_REPO_CLONING_TOKEN: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} + secrets: inherit diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index 99775f02..67bfc57e 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -11,13 +11,11 @@ on: description: "The git tag to generate SBOM for - used in scheduled runs" required: false type: string - secrets: - PRIVATE_REPO_CLONING_TOKEN: - description: "Cloning token" - required: true jobs: create-sbom: + permissions: + contents: write runs-on: [self-hosted, Linux, X64] steps: @@ -33,27 +31,26 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - submodules: recursive ref: ${{ steps.vars.outputs.TAG_NAME }} - token: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} + submodules: recursive - name: Create SBOM with Trivy uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' - format: 'spdx-json' + scan-type: "fs" + format: "spdx-json" output: "defguard-mobile-${{ steps.vars.outputs.VERSION }}.sbom.json" - scan-ref: '.' + scan-ref: "." severity: "CRITICAL,HIGH,MEDIUM,LOW" scanners: "vuln" - name: Create security advisory file with Trivy uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' - format: 'json' + scan-type: "fs" + format: "json" output: "defguard-mobile-${{ steps.vars.outputs.VERSION }}.advisories.json" - scan-ref: '.' + scan-ref: "." severity: "CRITICAL,HIGH,MEDIUM,LOW" scanners: "vuln" diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c5edab10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.envrc +.direnv/ From 5c681e23c3feeb6f2762c1927eecc305dd30c1e8 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 20 Jan 2026 14:04:20 +0100 Subject: [PATCH 05/65] Omit IPA from release artefacts; bump dependencies (#168) --- .github/workflows/build.yaml | 20 +-- .github/workflows/lint-and-test.yaml | 8 +- .github/workflows/sbom.yaml | 2 +- client/ios/Podfile.lock | 13 +- client/pubspec.lock | 176 +++++++++++++++------------ 5 files changed, 114 insertions(+), 105 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 35ad3ca5..2d5f4733 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: working-directory: ./client steps: - name: Checkout main repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: "recursive" @@ -29,7 +29,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.35.7 + flutter-version: 3.38.7 - name: Use homebrew ruby run: | @@ -66,14 +66,6 @@ jobs: api-key-id: ${{ secrets.ASC_API_KEY_ID }} api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} - - name: Upload iOS Artifact - uses: actions/upload-artifact@v4 - if: startsWith(github.ref, 'refs/tags/') - with: - name: ios-app - path: "client/build/ios/ipa/Defguard.ipa" - retention-days: 2 - build-android: runs-on: [self-hosted, macOS] env: @@ -83,7 +75,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v3 @@ -95,7 +87,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.7 + flutter-version: 3.38.7 - name: Accept licenses run: yes | flutter doctor --android-licenses @@ -147,7 +139,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v3 @@ -159,7 +151,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.7 + flutter-version: 3.38.7 - name: Install Android SDK components run: | diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 01df6ba9..c99855df 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Scan code with Trivy uses: aquasecurity/trivy-action@0.33.1 @@ -45,7 +45,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.4 + flutter-version: 3.38.7 - name: get deps run: flutter pub get @@ -63,7 +63,7 @@ jobs: # steps: # - name: Checkout - # uses: actions/checkout@v4 + # uses: actions/checkout@v6 # with: # submodules: "recursive" @@ -71,7 +71,7 @@ jobs: # uses: subosito/flutter-action@v2 # with: # channel: stable - # flutter-version: 3.32.6 + # flutter-version: 3.38.7 # - name: get deps # run: flutter pub get diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index 67bfc57e..e8276106 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -29,7 +29,7 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ steps.vars.outputs.TAG_NAME }} submodules: recursive diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index 2126ffd7..fff8239e 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -18,9 +18,6 @@ PODS: - FlutterMacOS - package_info_plus (0.4.5): - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - share_plus (0.0.1): @@ -66,7 +63,6 @@ DEPENDENCIES: - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -97,8 +93,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/mobile_scanner/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" share_plus: @@ -119,16 +113,15 @@ SPEC CHECKSUMS: flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/pubspec.lock b/client/pubspec.lock index c1b1ac58..990a071a 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.1.1" build_resolvers: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" url: "https://pub.dev" source: hosted - version: "8.12.0" + version: "8.12.3" characters: dependency: transitive description: @@ -233,14 +233,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: ae0db647e668cbb295a3527f0938e4039e004c80099dce2f964102373f5ce0b5 + url: "https://pub.dev" + source: hosted + version: "0.19.10" code_builder: dependency: transitive description: name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.11.1" collection: dependency: "direct main" description: @@ -277,18 +285,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.5+1" crypto: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" csslib: dependency: transitive description: @@ -437,10 +445,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" file: dependency: transitive description: @@ -498,10 +506,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5" + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" url: "https://pub.dev" source: hosted - version: "19.4.2" + version: "19.5.0" flutter_local_notifications_linux: dependency: transitive description: @@ -530,18 +538,18 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 url: "https://pub.dev" source: hosted - version: "2.0.30" + version: "2.0.33" flutter_riverpod: dependency: "direct main" description: @@ -610,10 +618,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter @@ -688,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "5410b9f4f6c9f01e8ff0eb81c9801ea13a3c3d39f8f0b1613cda08e27eab3c18" + url: "https://pub.dev" + source: hosted + version: "0.20.5" hooks_riverpod: dependency: "direct main" description: @@ -716,10 +732,10 @@ packages: dependency: "direct main" description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -740,10 +756,10 @@ packages: dependency: transitive description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.7.2" intl: dependency: transitive description: @@ -828,26 +844,26 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" + sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 url: "https://pub.dev" source: hosted - version: "1.0.52" + version: "1.0.56" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.6.1" local_auth_platform_interface: dependency: transitive description: name: local_auth_platform_interface - sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 url: "https://pub.dev" source: hosted - version: "1.0.10" + version: "1.1.0" local_auth_windows: dependency: transitive description: @@ -900,10 +916,18 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "5e7e09d904dc01de071b79b3f3789b302b0ed3c9c963109cd3f83ad90de62ecf" + sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044 + url: "https://pub.dev" + source: hosted + version: "7.1.4" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce url: "https://pub.dev" source: hosted - version: "7.1.2" + version: "0.17.2" node_preamble: dependency: transitive description: @@ -912,6 +936,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "55eb67ede1002d9771b3f9264d2c9d30bc364f0267bc1c6cc0883280d5f0c7cb" + url: "https://pub.dev" + source: hosted + version: "9.2.2" package_config: dependency: transitive description: @@ -964,18 +996,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e url: "https://pub.dev" source: hosted - version: "2.2.18" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -1180,26 +1212,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.18" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -1309,22 +1341,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" sqlite3: dependency: transitive description: name: sqlite3 - sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.9.4" sqlite3_flutter_libs: dependency: "direct main" description: @@ -1481,10 +1505,10 @@ packages: dependency: transitive description: name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.1" url_launcher: dependency: "direct main" description: @@ -1497,34 +1521,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" url: "https://pub.dev" source: hosted - version: "6.3.20" + version: "6.3.28" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "6.3.6" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1537,26 +1561,26 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" uuid: dependency: "direct main" description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.5.2" vector_graphics: dependency: transitive description: @@ -1593,18 +1617,18 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.1" web: dependency: transitive description: @@ -1641,10 +1665,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.15.0" win32_registry: dependency: transitive description: @@ -1693,5 +1717,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.32.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" From f31248daf52f3477c19fa46242c5e4c1483e5101 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 4 Feb 2026 14:04:14 +0100 Subject: [PATCH 06/65] iOS: clamp IPv6 prefix to /120 (#170) --- client/ios/VPNExtension/IpAddrMask.swift | 76 ++++++++++++------ .../VPNExtension/TunnelConfiguration.swift | 77 +++++++++++++++---- client/ios/boringtun | 2 +- client/pubspec.lock | 36 ++++----- client/pubspec.yaml | 43 +++++------ 5 files changed, 153 insertions(+), 81 deletions(-) diff --git a/client/ios/VPNExtension/IpAddrMask.swift b/client/ios/VPNExtension/IpAddrMask.swift index 53a0a569..50a5ef9f 100644 --- a/client/ios/VPNExtension/IpAddrMask.swift +++ b/client/ios/VPNExtension/IpAddrMask.swift @@ -51,28 +51,34 @@ struct IpAddrMask: Codable, Equatable { let address_data = try values.decode(Data.self, forKey: .address) switch address_data.count { - case 4: - guard let ipv4 = IPv4Address(address_data) else { - throw DecodingError - .dataCorrupted(DecodingError.Context( + case 4: + guard let ipv4 = IPv4Address(address_data) else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Unable to decode IP v4 address" )) - } - address = ipv4 - case 16: - guard let ipv6 = IPv6Address(address_data) else { - throw DecodingError - .dataCorrupted(DecodingError.Context( + } + address = ipv4 + case 16: + guard let ipv6 = IPv6Address(address_data) else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Unable to decode IP v6 address" )) - } - address = ipv6 - default: - throw DecodingError.typeMismatch(IpAddrMask.self, DecodingError.Context( + } + address = ipv6 + default: + throw DecodingError.typeMismatch( + IpAddrMask.self, + DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Invalid IP address length" )) } @@ -96,17 +102,18 @@ struct IpAddrMask: Codable, Equatable { // Note: UInt128 is available since iOS 18. Use UInt64 implementation. if address is IPv6Address { var bytes = Data(count: 16) - let (mask_upper, mask_lower) = if cidr < 64 { - ( - cidr == 0 ? UInt64.min : UInt64.max << (64 - cidr), - UInt64.min - ) - } else { - ( - UInt64.max, - (cidr - 64) == 0 ? UInt64.min : UInt64.max << (128 - cidr) - ) - } + let (mask_upper, mask_lower) = + if cidr < 64 { + ( + cidr == 0 ? UInt64.min : UInt64.max << (64 - cidr), + UInt64.min + ) + } else { + ( + UInt64.max, + (cidr - 64) == 0 ? UInt64.min : UInt64.max << (128 - cidr) + ) + } for i in 0...7 { bytes[i] = UInt8(truncatingIfNeeded: mask_upper >> (56 - i * 8)) } @@ -117,4 +124,23 @@ struct IpAddrMask: Codable, Equatable { } fatalError() } + + /// Return address with the mask applied. + func maskedAddress() -> IPAddress { + let subnet = mask().rawValue + var masked = Data(address.rawValue) + if subnet.count != masked.count { + fatalError() + } + for i in 0.. ([NEIPv4Route], [NEIPv6Route]) { + var ipv4IncludedRoutes = [NEIPv4Route]() + var ipv6IncludedRoutes = [NEIPv6Route]() + + // Routes to interface addresses. + for addr_mask in interface.addresses { + if addr_mask.address is IPv4Address { + let route = NEIPv4Route( + destinationAddress: "\(addr_mask.maskedAddress())", + subnetMask: "\(addr_mask.mask())") + route.gatewayAddress = "\(addr_mask.address)" + ipv4IncludedRoutes.append(route) + } else if addr_mask.address is IPv6Address { + let route = NEIPv6Route( + destinationAddress: "\(addr_mask.maskedAddress())", + networkPrefixLength: NSNumber(value: addr_mask.cidr) + ) + route.gatewayAddress = "\(addr_mask.address)" + ipv6IncludedRoutes.append(route) + } + } + + // Routes to peer's allowed IPs. + for peer in peers { + for addr_mask in peer.allowedIPs { + if addr_mask.address is IPv4Address { + ipv4IncludedRoutes.append( + NEIPv4Route( + destinationAddress: "\(addr_mask.address)", + subnetMask: "\(addr_mask.mask())")) + } else if addr_mask.address is IPv6Address { + ipv6IncludedRoutes.append( + NEIPv6Route( + destinationAddress: "\(addr_mask.address)", + networkPrefixLength: NSNumber(value: addr_mask.cidr))) + } + } + } + + return (ipv4IncludedRoutes, ipv6IncludedRoutes) + } + /// Helper function allowing to parse comma-separated string of addresses. private func parseAddresses(fromString string: String) -> [IpAddrMask] { var addresses: [IpAddrMask] = [] - for addr in string.split(separator: ",").map({ String($0.trimmingCharacters(in: .whitespaces)) }) { + for addr in string.split(separator: ",").map({ + String($0.trimmingCharacters(in: .whitespaces)) + }) { if let addr_mask = IpAddrMask(fromString: addr) { addresses.append(addr_mask) } @@ -87,9 +132,10 @@ final class TunnelConfiguration: Codable { interface.addresses = self.parseAddresses(fromString: startData.address) // DNS settings - let dnsRecords = startData.dns?.split(separator: ",").map { - $0.trimmingCharacters(in: .whitespaces) - } ?? [] + let dnsRecords = + startData.dns?.split(separator: ",").map { + $0.trimmingCharacters(in: .whitespaces) + } ?? [] if !dnsRecords.isEmpty { for record in dnsRecords { if IPv4Address(record) != nil || IPv6Address(record) != nil { @@ -104,15 +150,16 @@ final class TunnelConfiguration: Codable { peer.preSharedKey = startData.presharedKey peer.endpoint = Endpoint(from: startData.endpoint) peer.persistentKeepAlive = UInt16(startData.keepalive) - peer.allowedIPs = switch startData.traffic { + peer.allowedIPs = + switch startData.traffic { case .All: [ IpAddrMask(address: IPv4Address.any, cidr: 0), - IpAddrMask(address: IPv6Address.any, cidr: 0) + IpAddrMask(address: IPv6Address.any, cidr: 0), ] case .Predefined: self.parseAddresses(fromString: startData.allowedIps) - } + } } } diff --git a/client/ios/boringtun b/client/ios/boringtun index f47e80a9..8fe9b1ed 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit f47e80a96923733bb9ed2bd5590f882dfb1d9b95 +Subproject commit 8fe9b1edee32e6c1f64b3b2b2c819b199a3a80da diff --git a/client/pubspec.lock b/client/pubspec.lock index 990a071a..86f61570 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: code_assets - sha256: ae0db647e668cbb295a3527f0938e4039e004c80099dce2f964102373f5ce0b5 + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" url: "https://pub.dev" source: hosted - version: "0.19.10" + version: "1.0.0" code_builder: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.5+1" + version: "0.3.5+2" crypto: dependency: transitive description: @@ -381,10 +381,10 @@ packages: dependency: "direct main" description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.9.1" dio_cookie_manager: dependency: "direct main" description: @@ -700,10 +700,10 @@ packages: dependency: transitive description: name: hooks - sha256: "5410b9f4f6c9f01e8ff0eb81c9801ea13a3c3d39f8f0b1613cda08e27eab3c18" + sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "1.0.1" hooks_riverpod: dependency: "direct main" description: @@ -828,10 +828,10 @@ packages: dependency: transitive description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" local_auth: dependency: "direct main" description: @@ -924,10 +924,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" url: "https://pub.dev" source: hosted - version: "0.17.2" + version: "0.17.4" node_preamble: dependency: transitive description: @@ -940,10 +940,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "55eb67ede1002d9771b3f9264d2c9d30bc364f0267bc1c6cc0883280d5f0c7cb" + sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e" url: "https://pub.dev" source: hosted - version: "9.2.2" + version: "9.2.5" package_config: dependency: transitive description: @@ -1220,10 +1220,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f url: "https://pub.dev" source: hosted - version: "2.4.18" + version: "2.4.20" shared_preferences_foundation: dependency: transitive description: @@ -1601,10 +1601,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.1.20" vector_math: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index ad1c57d9..a307e5eb 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -2,7 +2,7 @@ name: mobile description: "Defguard mobile client" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.0+1 +version: 1.6.1+1 environment: sdk: ^3.8.1 @@ -101,34 +101,33 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true assets: - - assets/icons/ + - assets/icons/ fonts: - - family: Roboto - fonts: - - asset: assets/fonts/roboto-400.ttf - weight: 400 - - asset: assets/fonts/roboto-500.ttf - weight: 500 - - asset: assets/fonts/roboto-600.ttf - weight: 600 - - family: Poppins - fonts: - - asset: assets/fonts/poppins-300.ttf - weight: 300 - - asset: assets/fonts/poppins-400.ttf - weight: 400 - - asset: assets/fonts/poppins-500.ttf - weight: 500 - - asset: assets/fonts/poppins-600.ttf - weight: 600 + - family: Roboto + fonts: + - asset: assets/fonts/roboto-400.ttf + weight: 400 + - asset: assets/fonts/roboto-500.ttf + weight: 500 + - asset: assets/fonts/roboto-600.ttf + weight: 600 + - family: Poppins + fonts: + - asset: assets/fonts/poppins-300.ttf + weight: 300 + - asset: assets/fonts/poppins-400.ttf + weight: 400 + - asset: assets/fonts/poppins-500.ttf + weight: 500 + - asset: assets/fonts/poppins-600.ttf + weight: 600 # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg From bdfebf8791d046106fbe24fa91711c67bf55e793 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 12 Feb 2026 14:51:53 +0100 Subject: [PATCH 07/65] Upgrade BoringTun bindings (#172) --- .github/workflows/build.yaml | 6 +++++- .github/workflows/lint-and-test.yaml | 2 ++ .github/workflows/sbom.yaml | 2 +- client/ios/Runner.xcodeproj/project.pbxproj | 15 ++++++--------- client/ios/boringtun | 2 +- client/pubspec.lock | 20 ++++++++++---------- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2d5f4733..65dcf456 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,7 +29,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.9 - name: Use homebrew ruby run: | @@ -76,6 +76,8 @@ jobs: working-directory: ./client steps: - uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Set up Java uses: actions/setup-java@v3 @@ -140,6 +142,8 @@ jobs: working-directory: ./client steps: - uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Set up Java uses: actions/setup-java@v3 diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index c99855df..28834a67 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Scan code with Trivy uses: aquasecurity/trivy-action@0.33.1 diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index e8276106..fd1cd501 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ steps.vars.outputs.TAG_NAME }} - submodules: recursive + submodules: "recursive" - name: Create SBOM with Trivy uses: aquasecurity/trivy-action@0.33.1 diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 528e33b9..1e87f487 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -642,10 +642,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", - "$(PROJECT_DIR)/VPNExtension/BoringTun-old", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.6.1; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -660,7 +659,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/boringtunFFI.h"; + SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = ""; @@ -697,10 +696,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", - "$(PROJECT_DIR)/VPNExtension/BoringTun-old", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.6.1; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -713,7 +711,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/boringtunFFI.h"; + SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = ""; TARGETED_DEVICE_FAMILY = "1,2"; @@ -749,10 +747,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", - "$(PROJECT_DIR)/VPNExtension/BoringTun-old", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.6.1; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -765,7 +762,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/boringtunFFI.h"; + SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = ""; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/client/ios/boringtun b/client/ios/boringtun index 8fe9b1ed..46453492 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit 8fe9b1edee32e6c1f64b3b2b2c819b199a3a80da +Subproject commit 46453492245605418b13b79019b27a7b4427349b diff --git a/client/pubspec.lock b/client/pubspec.lock index 86f61570..7b27e0af 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -357,10 +357,10 @@ packages: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" device_info_plus: dependency: "direct main" description: @@ -445,10 +445,10 @@ packages: dependency: transitive description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" file: dependency: transitive description: @@ -940,10 +940,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e" + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" url: "https://pub.dev" source: hosted - version: "9.2.5" + version: "9.3.0" package_config: dependency: transitive description: @@ -1337,10 +1337,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" sqlite3: dependency: transitive description: @@ -1529,10 +1529,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + sha256: b1aca26728b7cc7a3af971bb6f601554a8ae9df2e0a006de8450ba06a17ad36a url: "https://pub.dev" source: hosted - version: "6.3.6" + version: "6.4.0" url_launcher_linux: dependency: transitive description: From 94f54d402149b8c584a9608d768d5a4e8d212f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 13 Feb 2026 11:32:49 +0100 Subject: [PATCH 08/65] Update BoringTun with better bindings.sh --- client/.gitignore | 1 - client/ios/boringtun | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/.gitignore b/client/.gitignore index 72abeecf..975efdc6 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -50,5 +50,4 @@ app.*.map.json .envrc local.properties -ios/boringtun ios/VPNExtension/BoringTun diff --git a/client/ios/boringtun b/client/ios/boringtun index 46453492..df20f088 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit 46453492245605418b13b79019b27a7b4427349b +Subproject commit df20f088a306b449786441a911da69621b8ee2fe From 1a606e5c6398eddafaaad61e82920fdc5c747870 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:37:34 +0100 Subject: [PATCH 09/65] Version reporting (#173) --- README.md | 33 ++-- client/ios/Podfile.lock | 28 ++-- .../data/proto/client_platform_info.pb.dart | 148 ++++++++++++++++++ .../proto/client_platform_info.pbenum.dart | 11 ++ .../proto/client_platform_info.pbjson.dart | 77 +++++++++ client/lib/open/api.dart | 46 ++++++ .../add_instance/add_instance_screen.dart | 8 +- client/proto/client_platform_info.proto | 13 ++ client/pubspec.lock | 8 + client/pubspec.yaml | 1 + 10 files changed, 338 insertions(+), 35 deletions(-) create mode 100644 client/lib/data/proto/client_platform_info.pb.dart create mode 100644 client/lib/data/proto/client_platform_info.pbenum.dart create mode 100644 client/lib/data/proto/client_platform_info.pbjson.dart create mode 100644 client/proto/client_platform_info.proto diff --git a/README.md b/README.md index c166c55c..6bf4e750 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,16 @@ The **DefGuard Mobile Client** is a secure, self-hosted **WireGuard VPN mobile c This open-source, cross-platform VPN app supports easy QR code onboarding and flexible traffic routing to meet diverse secure remote access needs. DefGuard is part of a modular ecosystem built for VPN orchestration and identity management . Defguard provides a mobile VPN client with biometrics and TOTP. ## Key Features -- Secure **WireGuard VPN mobile client** with **Multi-Factor Authentication (MFA)** -- Internal SSO/OIDC support with biometrics, TOTP, email verification -- External SSO support: Google, Okta, Microsoft EntraID, JumpCloud, and more -- Quick and easy onboarding via secure **QR code VPN onboarding** or URL/token -- Flexible traffic routing: all traffic via VPN or selective routing -- Real-time synchronization of VPN configurations with the DefGuard server -- Native **cross-platform VPN app** support for **Android VPN client** and iOS VPN client -- Fully **self-hosted VPN solution** for ultimate privacy and control -- Open-source codebase for transparency and customization + +- Secure **WireGuard VPN mobile client** with **Multi-Factor Authentication (MFA)** +- Internal SSO/OIDC support with biometrics, TOTP, email verification +- External SSO support: Google, Okta, Microsoft EntraID, JumpCloud, and more +- Quick and easy onboarding via secure **QR code VPN onboarding** or URL/token +- Flexible traffic routing: all traffic via VPN or selective routing +- Real-time synchronization of VPN configurations with the DefGuard server +- Native **cross-platform VPN app** support for **Android VPN client** and iOS VPN client +- Fully **self-hosted VPN solution** for ultimate privacy and control +- Open-source codebase for transparency and customization ## Screenshots @@ -24,24 +25,26 @@ This open-source, cross-platform VPN app supports easy QR code onboarding and fl Instance list MFA defguard IdP - ## Getting Started -You need to have a running [Defguard Server](https://github.com/DefGuard/defguard) to use the mobile app. +You need to have a running [Defguard Core](https://github.com/DefGuard/defguard) to use the mobile app. ### Install the App Join closed beta for iOS or Android. #### Android + - Download from [Google Play](https://play.google.com/store/apps/details?id=net.defguard.mobile) #### iOS -- Available soon on the [App Store](https://testflight.apple.com/join/Jvdhkt7h) -Documentation available at : [https://docs.defguard.net/help/mobile-client](https://docs.defguard.net/help/mobile-client) +- Available soon on the [App Store](https://apps.apple.com/us/app/defguard-vpn-client/id6748068630) + +Documentation available at : [https://docs.defguard.net/using-defguard-for-end-users/mobile-client](https://docs.defguard.net/using-defguard-for-end-users/mobile-client) ## About DefGuard -DefGuard is a comprehensive platform offering **secure remote access**, **identity management**, and VPN orchestration with a focus on security using **multi-factor authentication for VPN**. -Visit defguard.net for more information. \ No newline at end of file +DefGuard is a comprehensive platform offering **secure remote access**, **identity management**, and VPN orchestration with a focus on security using **multi-factor authentication for VPN**. + +Visit defguard.net for more information. diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index fff8239e..fec2a9df 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -107,22 +107,22 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 - mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 - url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa - wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e + sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/lib/data/proto/client_platform_info.pb.dart b/client/lib/data/proto/client_platform_info.pb.dart new file mode 100644 index 00000000..bdfab624 --- /dev/null +++ b/client/lib/data/proto/client_platform_info.pb.dart @@ -0,0 +1,148 @@ +// This is a generated file - do not edit. +// +// Generated from client_platform_info.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +class ClientPlatformInfo extends $pb.GeneratedMessage { + factory ClientPlatformInfo({ + $core.String? osFamily, + $core.String? osType, + $core.String? version, + $core.String? edition, + $core.String? codename, + $core.String? bitness, + $core.String? architecture, + }) { + final result = create(); + if (osFamily != null) result.osFamily = osFamily; + if (osType != null) result.osType = osType; + if (version != null) result.version = version; + if (edition != null) result.edition = edition; + if (codename != null) result.codename = codename; + if (bitness != null) result.bitness = bitness; + if (architecture != null) result.architecture = architecture; + return result; + } + + ClientPlatformInfo._(); + + factory ClientPlatformInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientPlatformInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientPlatformInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'defguard.proxy'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'osFamily') + ..aOS(2, _omitFieldNames ? '' : 'osType') + ..aOS(3, _omitFieldNames ? '' : 'version') + ..aOS(4, _omitFieldNames ? '' : 'edition') + ..aOS(5, _omitFieldNames ? '' : 'codename') + ..aOS(6, _omitFieldNames ? '' : 'bitness') + ..aOS(7, _omitFieldNames ? '' : 'architecture') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientPlatformInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientPlatformInfo copyWith(void Function(ClientPlatformInfo) updates) => + super.copyWith((message) => updates(message as ClientPlatformInfo)) + as ClientPlatformInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientPlatformInfo create() => ClientPlatformInfo._(); + @$core.override + ClientPlatformInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientPlatformInfo getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientPlatformInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get osFamily => $_getSZ(0); + @$pb.TagNumber(1) + set osFamily($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasOsFamily() => $_has(0); + @$pb.TagNumber(1) + void clearOsFamily() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get osType => $_getSZ(1); + @$pb.TagNumber(2) + set osType($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasOsType() => $_has(1); + @$pb.TagNumber(2) + void clearOsType() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get version => $_getSZ(2); + @$pb.TagNumber(3) + set version($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasVersion() => $_has(2); + @$pb.TagNumber(3) + void clearVersion() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get edition => $_getSZ(3); + @$pb.TagNumber(4) + set edition($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasEdition() => $_has(3); + @$pb.TagNumber(4) + void clearEdition() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get codename => $_getSZ(4); + @$pb.TagNumber(5) + set codename($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasCodename() => $_has(4); + @$pb.TagNumber(5) + void clearCodename() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get bitness => $_getSZ(5); + @$pb.TagNumber(6) + set bitness($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasBitness() => $_has(5); + @$pb.TagNumber(6) + void clearBitness() => $_clearField(6); + + @$pb.TagNumber(7) + $core.String get architecture => $_getSZ(6); + @$pb.TagNumber(7) + set architecture($core.String value) => $_setString(6, value); + @$pb.TagNumber(7) + $core.bool hasArchitecture() => $_has(6); + @$pb.TagNumber(7) + void clearArchitecture() => $_clearField(7); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/client/lib/data/proto/client_platform_info.pbenum.dart b/client/lib/data/proto/client_platform_info.pbenum.dart new file mode 100644 index 00000000..6160acdf --- /dev/null +++ b/client/lib/data/proto/client_platform_info.pbenum.dart @@ -0,0 +1,11 @@ +// This is a generated file - do not edit. +// +// Generated from client_platform_info.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports diff --git a/client/lib/data/proto/client_platform_info.pbjson.dart b/client/lib/data/proto/client_platform_info.pbjson.dart new file mode 100644 index 00000000..a30a95cb --- /dev/null +++ b/client/lib/data/proto/client_platform_info.pbjson.dart @@ -0,0 +1,77 @@ +// This is a generated file - do not edit. +// +// Generated from client_platform_info.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use clientPlatformInfoDescriptor instead') +const ClientPlatformInfo$json = { + '1': 'ClientPlatformInfo', + '2': [ + {'1': 'os_family', '3': 1, '4': 1, '5': 9, '10': 'osFamily'}, + {'1': 'os_type', '3': 2, '4': 1, '5': 9, '10': 'osType'}, + {'1': 'version', '3': 3, '4': 1, '5': 9, '10': 'version'}, + { + '1': 'edition', + '3': 4, + '4': 1, + '5': 9, + '9': 0, + '10': 'edition', + '17': true + }, + { + '1': 'codename', + '3': 5, + '4': 1, + '5': 9, + '9': 1, + '10': 'codename', + '17': true + }, + { + '1': 'bitness', + '3': 6, + '4': 1, + '5': 9, + '9': 2, + '10': 'bitness', + '17': true + }, + { + '1': 'architecture', + '3': 7, + '4': 1, + '5': 9, + '9': 3, + '10': 'architecture', + '17': true + }, + ], + '8': [ + {'1': '_edition'}, + {'1': '_codename'}, + {'1': '_bitness'}, + {'1': '_architecture'}, + ], +}; + +/// Descriptor for `ClientPlatformInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientPlatformInfoDescriptor = $convert.base64Decode( + 'ChJDbGllbnRQbGF0Zm9ybUluZm8SGwoJb3NfZmFtaWx5GAEgASgJUghvc0ZhbWlseRIXCgdvc1' + '90eXBlGAIgASgJUgZvc1R5cGUSGAoHdmVyc2lvbhgDIAEoCVIHdmVyc2lvbhIdCgdlZGl0aW9u' + 'GAQgASgJSABSB2VkaXRpb26IAQESHwoIY29kZW5hbWUYBSABKAlIAVIIY29kZW5hbWWIAQESHQ' + 'oHYml0bmVzcxgGIAEoCUgCUgdiaXRuZXNziAEBEicKDGFyY2hpdGVjdHVyZRgHIAEoCUgDUgxh' + 'cmNoaXRlY3R1cmWIAQFCCgoIX2VkaXRpb25CCwoJX2NvZGVuYW1lQgoKCF9iaXRuZXNzQg8KDV' + '9hcmNoaXRlY3R1cmU='); diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 6b996bd5..6641000a 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -1,10 +1,14 @@ +import 'dart:convert'; import 'dart:io'; import 'package:cookie_jar/cookie_jar.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:mobile/data/db/enums.dart'; +import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:mobile/data/proxy/mfa.dart'; @@ -44,6 +48,48 @@ class _ProxyApi { final cookieJar = CookieJar(); _dio.interceptors.add(CookieManager(cookieJar)); _dio.interceptors.add(TalkerDioLogger(talker: talker)); + _initHeaders(); + } + + Future _initHeaders() async { + try { + final deviceInfo = DeviceInfoPlugin(); + final ClientPlatformInfo platformInfo; + + if (Platform.isAndroid) { + final android = await deviceInfo.androidInfo; + platformInfo = ClientPlatformInfo( + osType: 'Android', + version: android.version.release, + codename: android.version.codename, + architecture: android.supportedAbis.first, + bitness: '64', + ); + } else if (Platform.isIOS) { + final ios = await deviceInfo.iosInfo; + platformInfo = ClientPlatformInfo( + osType: 'iOS', + version: ios.systemVersion, + architecture: 'arm64', + bitness: '64', + ); + } else { + platformInfo = ClientPlatformInfo( + osFamily: Platform.operatingSystem, + osType: Platform.operatingSystem, + version: Platform.operatingSystemVersion, + ); + } + + final platformBytes = platformInfo.writeToBuffer(); + final platformBase64 = base64Encode(platformBytes); + + final packageInfo = await PackageInfo.fromPlatform(); + _dio.options.headers['defguard-client-version'] = packageInfo.version; + _dio.options.headers['defguard-client-platform'] = platformBase64; + } catch (e) { + talker.error("Failed to set client headers", e); + } } Future<(ConfigurationPollResponse?, int?, Headers?)> pollConfiguration( diff --git a/client/lib/open/screens/add_instance/add_instance_screen.dart b/client/lib/open/screens/add_instance/add_instance_screen.dart index df16e36d..6c1a27b3 100644 --- a/client/lib/open/screens/add_instance/add_instance_screen.dart +++ b/client/lib/open/screens/add_instance/add_instance_screen.dart @@ -77,9 +77,7 @@ class AddInstanceScreen extends HookConsumerWidget { if (isAgreed ?? false) { if (context.mounted) { QRScreenRoute( - QrScreenData( - intent: QrScreenIntent.addInstance, - ), + QrScreenData(intent: QrScreenIntent.addInstance), ).push(context); } } else { @@ -92,9 +90,7 @@ class AddInstanceScreen extends HookConsumerWidget { await asyncPrefs.setBool(agreementPrefsKey, true); if (context.mounted) { QRScreenRoute( - QrScreenData( - intent: QrScreenIntent.addInstance, - ), + QrScreenData(intent: QrScreenIntent.addInstance), ).push(context); } } diff --git a/client/proto/client_platform_info.proto b/client/proto/client_platform_info.proto new file mode 100644 index 00000000..c90a3ef1 --- /dev/null +++ b/client/proto/client_platform_info.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package defguard.proxy; + +message ClientPlatformInfo { + string os_family = 1; + string os_type = 2; + string version = 3; + optional string edition = 4; + optional string codename = 5; + optional string bitness = 6; + optional string architecture = 7; +} diff --git a/client/pubspec.lock b/client/pubspec.lock index 7b27e0af..1105e881 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1120,6 +1120,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + protobuf: + dependency: "direct main" + description: + name: protobuf + sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" + url: "https://pub.dev" + source: hosted + version: "6.0.0" pub_semver: dependency: "direct main" description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index a307e5eb..17fc6ad3 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: shared_preferences: ^2.5.3 flutter_secure_storage: ^9.2.4 device_info_plus: ^11.5.0 + protobuf: ^6.0.0 dev_dependencies: flutter_test: From 50fc76ea9f3b1f1398d5476e235612dadc099c25 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 23 Feb 2026 11:25:51 +0100 Subject: [PATCH 10/65] Fix IpAddrMask (#176) --- .github/workflows/lint-and-test.yaml | 2 +- .github/workflows/sbom.yaml | 4 +-- client/ios/Podfile.lock | 28 ++++++++-------- client/ios/Runner.xcodeproj/project.pbxproj | 6 ++-- client/ios/VPNExtension/IpAddrMask.swift | 5 ++- client/ios/boringtun | 2 +- client/pubspec.lock | 36 ++++++++++----------- client/pubspec.yaml | 2 +- 8 files changed, 44 insertions(+), 41 deletions(-) diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 28834a67..46bcafa3 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -34,7 +34,7 @@ jobs: submodules: "recursive" - name: Scan code with Trivy - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" scan-ref: "." diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index fd1cd501..aea291c8 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -35,7 +35,7 @@ jobs: submodules: "recursive" - name: Create SBOM with Trivy - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" format: "spdx-json" @@ -45,7 +45,7 @@ jobs: scanners: "vuln" - name: Create security advisory file with Trivy - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" format: "json" diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index fec2a9df..fff8239e 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -107,22 +107,22 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 + mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 + sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa + wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 1e87f487..d8b5fda6 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -644,7 +644,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.1; + MARKETING_VERSION = 1.6.2; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -698,7 +698,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.1; + MARKETING_VERSION = 1.6.2; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -749,7 +749,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.1; + MARKETING_VERSION = 1.6.2; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; diff --git a/client/ios/VPNExtension/IpAddrMask.swift b/client/ios/VPNExtension/IpAddrMask.swift index 50a5ef9f..32f972d1 100644 --- a/client/ios/VPNExtension/IpAddrMask.swift +++ b/client/ios/VPNExtension/IpAddrMask.swift @@ -15,17 +15,20 @@ struct IpAddrMask: Codable, Equatable { separator: "/", maxSplits: 1, ) + let default_cidr: UInt8 if let ipv4 = IPv4Address(String(parts[0])) { address = ipv4 + default_cidr = 32 } else if let ipv6 = IPv6Address(String(parts[0])) { address = ipv6 + default_cidr = 128 } else { return nil } if parts.count > 1 { cidr = UInt8(parts[1]) ?? 0 } else { - cidr = 0 + cidr = default_cidr } } diff --git a/client/ios/boringtun b/client/ios/boringtun index df20f088..b990805f 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit df20f088a306b449786441a911da69621b8ee2fe +Subproject commit b990805fc1637eeaa401bc156adf0dd28b2e50b8 diff --git a/client/pubspec.lock b/client/pubspec.lock index 1105e881..000de1cc 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.0.9" args: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" + sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" url: "https://pub.dev" source: hosted - version: "8.12.3" + version: "8.12.4" characters: dependency: transitive description: @@ -756,10 +756,10 @@ packages: dependency: transitive description: name: image - sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.7.2" + version: "4.8.0" intl: dependency: transitive description: @@ -916,10 +916,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044 + sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce url: "https://pub.dev" source: hosted - version: "7.1.4" + version: "7.2.0" native_toolchain_c: dependency: transitive description: @@ -1084,10 +1084,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" platform: dependency: transitive description: @@ -1116,10 +1116,10 @@ packages: dependency: transitive description: name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.5.0" protobuf: dependency: "direct main" description: @@ -1537,10 +1537,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: b1aca26728b7cc7a3af971bb6f601554a8ae9df2e0a006de8450ba06a17ad36a + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.4.1" url_launcher_linux: dependency: transitive description: @@ -1585,10 +1585,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.3" vector_graphics: dependency: transitive description: @@ -1609,10 +1609,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" + sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" url: "https://pub.dev" source: hosted - version: "1.1.20" + version: "1.2.0" vector_math: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 17fc6ad3..29822c85 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.1+1 +version: 1.6.2+1 environment: sdk: ^3.8.1 From 1b293213402774590f45800b8def4f2f8a2f4ba0 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Fri, 27 Feb 2026 12:06:17 +0100 Subject: [PATCH 11/65] Added new issue templates (#177) --- .github/ISSUE_TEMPLATE/01-feature-request.yml | 56 ++++++++ .github/ISSUE_TEMPLATE/02-bug.yml | 125 ++++++++++++++++++ .github/ISSUE_TEMPLATE/03-internal.yml | 19 +++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ 4 files changed, 208 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/01-feature-request.yml create mode 100644 .github/ISSUE_TEMPLATE/02-bug.yml create mode 100644 .github/ISSUE_TEMPLATE/03-internal.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/01-feature-request.yml b/.github/ISSUE_TEMPLATE/01-feature-request.yml new file mode 100644 index 00000000..13b94106 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-feature-request.yml @@ -0,0 +1,56 @@ +name: Feature request +description: Suggest an idea or improvement for Defguard +title: "[Feature]: " +labels: + - feature +type: feature + +body: + - type: markdown + attributes: + value: | + Thank you for suggesting a feature. Your feedback helps improve Defguard. + + - type: textarea + id: problem + attributes: + label: Problem description + description: What problem are you trying to solve? Attach screenshots or recording if it helps illustrate the problem. + placeholder: | + Describe the limitation, friction, or missing capability. + Example: "Users cannot restrict access based on device posture..." + validations: + required: true + + - type: textarea + id: proposed_solution + attributes: + label: Proposed solution + description: Describe the solution you would like. Attach mockups or diagrams if you have them. + placeholder: | + Describe the desired behavior, API, UI, or workflow. + Include examples if possible. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Any alternative solutions or workarounds you've considered + placeholder: | + Example: "Currently we use separate locations, but this is hard to manage..." + validations: + required: false + + - type: dropdown + id: impact + attributes: + label: Impact + description: How important is this feature for you? + options: + - Nice to have + - Important + - Critical / blocking our usage + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/02-bug.yml b/.github/ISSUE_TEMPLATE/02-bug.yml new file mode 100644 index 00000000..fd045c3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-bug.yml @@ -0,0 +1,125 @@ +name: Bug report +description: Report a problem in Defguard so we can reproduce and fix it. +title: "[Bug]: " +labels: + - bug +type: bug + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug. + + **Privacy note:** This issue tracker is public. Do not include sensitive data such as secrets, private keys, tokens, passwords, internal domains, or customer data. All data should be anonymised and any secrets must be redacted. + + - type: textarea + id: summary + attributes: + label: Summary + description: A clear, one-paragraph description of what’s broken. + placeholder: "After enabling MFA for a user, Desktop client cannot start a session; it loops on …" + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Numbered steps help us reproduce reliably. Attach screenshots or recordings if they help illustrate the steps. + placeholder: | + 1. Go to … + 2. Click … + 3. Configure … + 4. Observe … + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What should happen if the bug were fixed? + placeholder: "VPN connects successfully and a session is established." + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behavior + description: What happens instead? Include error messages, screenshots, or recordings if they help illustrate the issue. + placeholder: "VPN connection fails with … / UI shows … / API returns …" + validations: + required: true + + - type: input + id: version + attributes: + label: Defguard version + description: Exact versions of all components. + placeholder: "Core: v1.6.0, Gateway: v1.6.0, Edge: v1.6.0, Desktop client: v1.6.0, Mobile client: v1.6.0." + validations: + required: true + + - type: input + id: environment + attributes: + label: Environment details + description: Operating systems of all components. + placeholder: "Core: Ubuntu 24.04, Gateway: Ubuntu 24.04, Edge: Ubuntu 24.04, Desktop client: macOS 15.x, Mobile client: iOS 18." + validations: + required: true + + - type: dropdown + id: deployment + attributes: + label: Deployment / install method + description: How is Defguard installed? + multiple: false + options: + - One-line script + - Standalone packages + - Docker / Docker Compose + - Kubernetes / Helm + - Terraform + - AMI + - Custom + - Not installed (WireGuard only) + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant logs / output + description: Paste only what’s necessary. Please redact secrets (tokens, private IPs, domains) if needed. Enable DEBUG log level if you can (https://docs.defguard.net/support-1/how-to-submit-an-issue#id-1.-enable-debug-logging). + render: shell + placeholder: | + # Core logs + [2024-06-01T12:00:00Z] ERROR: ... + + # Edge logs + [2024-06-01T12:00:00Z] ERROR: ... + + # Gateway logs + [2024-06-01T12:00:00Z] ERROR: ... + + # Desktop client logs (you can find them in the app's settings) + [2024-06-01T12:00:00Z] ERROR: ... + + # Mobile client logs (you can find them in the app's main menu - View Application Logs) + [2024-06-01T12:00:00Z] ERROR: ... + validations: + required: false + + - type: textarea + id: config + attributes: + label: Relevant configuration (redacted) + description: If configuration is involved (LDAP, OIDC, WireGuard, gRPC certs), paste the minimum snippet. + render: yaml + placeholder: | + # Redact secrets/certs/private keys + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/03-internal.yml b/.github/ISSUE_TEMPLATE/03-internal.yml new file mode 100644 index 00000000..97278a09 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-internal.yml @@ -0,0 +1,19 @@ +name: Internal issue +description: Internal Defguard team use only +labels: + - internal +type: task + +body: + - type: markdown + attributes: + value: | + This template is intended for internal Defguard team use. + + - type: textarea + id: description + attributes: + label: Description + placeholder: Detailed description, context, links, notes + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..7987ea49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Defguard Troubleshooting Guide + url: https://docs.defguard.net/support-1/troubleshooting + about: Make sure to check the troubleshooting guide before submitting an issue. It has solutions for common problems and can help you debug faster. + - name: Open a discussion to get help from the community + url: https://github.com/DefGuard/defguard/discussions/new/choose + about: Having trouble with Defguard deployment or configuration? Reach out to our community for help. From 278d056e23600125e21e468e6666836913d73395 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 6 May 2026 09:38:01 +0200 Subject: [PATCH 12/65] Use system CA's & fix signing (#181) --- .github/workflows/build.yaml | 14 +++++-- .github/workflows/lint-and-test.yaml | 2 +- .github/workflows/release.yaml | 4 +- .github/workflows/sbom.yaml | 4 +- .../android/app/src/main/AndroidManifest.xml | 1 + .../main/res/xml/network_security_config.xml | 9 +++++ client/lib/open/api.dart | 2 + client/pubspec.lock | 40 +++++++++++++++++++ client/pubspec.yaml | 1 + 9 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 client/android/app/src/main/res/xml/network_security_config.xml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 65dcf456..828c357a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -104,6 +104,7 @@ jobs: run: flutter build appbundle --release --build-number=${{ github.run_number }} - name: Sign AAB + id: sign_aab uses: r0adkll/sign-android-release@v1 with: releaseDirectory: client/build/app/outputs/bundle/release @@ -121,15 +122,18 @@ jobs: with: serviceAccountJsonPlainText: "${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}" packageName: net.defguard.mobile - releaseFiles: client/build/app/outputs/bundle/release/app-release.aab + releaseFiles: ${{ steps.sign_aab.outputs.signedReleaseFile }} track: internal + - name: Rename AAB + run: cp "${{ steps.sign_aab.outputs.signedReleaseFile }}" Defguard.aab + - name: Upload Android Artifact uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') with: name: android-app - path: "client/build/app/outputs/bundle/release/app-release.aab" + path: "client/Defguard.aab" retention-days: 2 build-android-apk: @@ -173,6 +177,7 @@ jobs: run: flutter build apk --release --build-number=${{ github.run_number }} - name: Sign APK + id: sign_apk uses: r0adkll/sign-android-release@v1 with: releaseDirectory: client/build/app/outputs/flutter-apk @@ -181,12 +186,15 @@ jobs: keyStorePassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" keyPassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" + - name: Rename APK + run: cp "${{ steps.sign_apk.outputs.signedReleaseFile }}" Defguard.apk + - name: Upload Android Artifact uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') with: name: android-app-apk - path: "client/build/app/outputs/flutter-apk/app-release.apk" + path: "client/Defguard.apk" retention-days: 2 release: diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 46bcafa3..2b719f3b 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -34,7 +34,7 @@ jobs: submodules: "recursive" - name: Scan code with Trivy - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" scan-ref: "." diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 103af535..a8d647f0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,8 +23,8 @@ jobs: draft: true files: | ./artifacts/Defguard.ipa - ./artifacts/app-release.aab - ./artifacts/app-release.apk + ./artifacts/Defguard.aab + ./artifacts/Defguard.apk create-sbom: needs: [create-release] diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index aea291c8..ec8c5cf3 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -35,7 +35,7 @@ jobs: submodules: "recursive" - name: Create SBOM with Trivy - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" format: "spdx-json" @@ -45,7 +45,7 @@ jobs: scanners: "vuln" - name: Create security advisory file with Trivy - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" format: "json" diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml index b6022268..72aadc7c 100644 --- a/client/android/app/src/main/AndroidManifest.xml +++ b/client/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ android:enableOnBackInvokedCallback="true" android:allowBackup="false" android:fullBackupContent="false" + android:networkSecurityConfig="@xml/network_security_config" android:dataExtractionRules="@xml/data_extraction_rules"> + + + + + + + + diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 6641000a..14168455 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -5,6 +5,7 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:native_dio_adapter/native_dio_adapter.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; @@ -45,6 +46,7 @@ class _ProxyApi { ); _ProxyApi._internal() { + _dio.httpClientAdapter = NativeAdapter(); final cookieJar = CookieJar(); _dio.interceptors.add(CookieManager(cookieJar)); _dio.interceptors.add(TalkerDioLogger(talker: talker)); diff --git a/client/pubspec.lock b/client/pubspec.lock index 000de1cc..dfff8949 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -281,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + cronet_http: + dependency: transitive + description: + name: cronet_http + sha256: "8e77bc6f203e0bc9126e6a9092508a3435dbcb04da3b53ed1a358909385c5e0e" + url: "https://pub.dev" + source: hosted + version: "1.8.0" cross_file: dependency: transitive description: @@ -305,6 +313,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + cupertino_http: + dependency: transitive + description: + name: cupertino_http + sha256: "82cbec60c90bf785a047a9525688b6dacac444e177e1d5a5876963d3c50369e8" + url: "https://pub.dev" + source: hosted + version: "2.4.0" cupertino_icons: dependency: "direct main" description: @@ -752,6 +768,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + http_profile: + dependency: transitive + description: + name: http_profile + sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78" + url: "https://pub.dev" + source: hosted + version: "0.1.0" image: dependency: transitive description: @@ -776,6 +800,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: "8706a77e94c76fe9ec9315e18949cc9479cc03af97085ca9c1077b61323ea12d" + url: "https://pub.dev" + source: hosted + version: "0.15.2" js: dependency: transitive description: @@ -920,6 +952,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.2.0" + native_dio_adapter: + dependency: "direct main" + description: + name: native_dio_adapter + sha256: "9bbfa5221fd287eb063962bbe6534290e5f87933e576fac210149fb80253b89a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" native_toolchain_c: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 29822c85..5c8feae3 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: cookie_jar: ^4.0.8 dio: ^5.8.0+1 dio_cookie_manager: ^3.2.0 + native_dio_adapter: ^1.3.0 flutter_native_splash: ^2.4.6 flutter_launcher_icons: ^0.14.4 flutter_svg: ^2.1.0 From 9fc08348b691302f9875507f6982f1ec6d2df1a7 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 15 May 2026 13:01:40 +0200 Subject: [PATCH 13/65] Display OpenID provider name (#185) --- .github/workflows/build.yaml | 4 +- .../defguard/drift_schema_v3.json | 1 + client/ios/Podfile.lock | 35 +- client/lib/data/db/database.dart | 11 +- client/lib/data/db/database.g.dart | 86 +- client/lib/data/db/database.steps.dart | 110 +- client/lib/data/proxy/enrollment.dart | 16 +- client/lib/data/proxy/enrollment.g.dart | 7 + .../screens/mfa/openid_mfa_screen.dart | 28 +- .../screens/name_device_screen.dart | 5 +- .../instance/services/tunnel_service.dart | 13 +- .../test/drift/defguard/generated/schema.dart | 5 +- .../drift/defguard/generated/schema_v3.dart | 1321 +++++++++++++++++ 13 files changed, 1602 insertions(+), 40 deletions(-) create mode 100644 client/drift_schemas/defguard/drift_schema_v3.json create mode 100644 client/test/drift/defguard/generated/schema_v3.dart diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 828c357a..0c65191e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -126,7 +126,7 @@ jobs: track: internal - name: Rename AAB - run: cp "${{ steps.sign_aab.outputs.signedReleaseFile }}" Defguard.aab + run: cp "$GITHUB_WORKSPACE/${{ steps.sign_aab.outputs.signedReleaseFile }}" Defguard.aab - name: Upload Android Artifact uses: actions/upload-artifact@v4 @@ -187,7 +187,7 @@ jobs: keyPassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" - name: Rename APK - run: cp "${{ steps.sign_apk.outputs.signedReleaseFile }}" Defguard.apk + run: cp "$GITHUB_WORKSPACE/${{ steps.sign_apk.outputs.signedReleaseFile }}" Defguard.apk - name: Upload Android Artifact uses: actions/upload-artifact@v4 diff --git a/client/drift_schemas/defguard/drift_schema_v3.json b/client/drift_schemas/defguard/drift_schema_v3.json new file mode 100644 index 00000000..ed25a86f --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v3.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"openid_display_name","getter_name":"openidDisplayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index fff8239e..c7747bb2 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -1,6 +1,9 @@ PODS: - app_links (6.4.1): - Flutter + - cupertino_http (0.0.1): + - Flutter + - FlutterMacOS - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) @@ -55,6 +58,7 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) + - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) @@ -77,6 +81,8 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" + cupertino_http: + :path: ".symlinks/plugins/cupertino_http/darwin" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: @@ -107,22 +113,23 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 - mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 - url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa - wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e + sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 363f784c..65d5e55f 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -45,6 +45,9 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey { // tells if the secure biometric storage exists for this instance BoolColumn get mfaKeysStored => boolean()(); + + // openid provider display name configured on the server side + TextColumn get openidDisplayName => text().nullable()(); } @DataClassName('Location') @@ -98,7 +101,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 2; + int get schemaVersion => 3; @override MigrationStrategy get migration { @@ -123,6 +126,12 @@ class AppDatabase extends _$AppDatabase { // 3. Drop old "disable_all_traffic" column await m.dropColumn(defguardInstances, "disable_all_traffic"); }, + from2To3: (m, schema) async { + await m.addColumn( + schema.defguardInstances, + schema.defguardInstances.openidDisplayName, + ); + }, ), ); } diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index 2c951d09..da5c8d3a 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -154,6 +154,18 @@ class $DefguardInstancesTable extends DefguardInstances 'CHECK ("mfa_keys_stored" IN (0, 1))', ), ); + static const VerificationMeta _openidDisplayNameMeta = const VerificationMeta( + 'openidDisplayName', + ); + @override + late final GeneratedColumn openidDisplayName = + GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); @override List get $columns => [ id, @@ -169,6 +181,7 @@ class $DefguardInstancesTable extends DefguardInstances pubKey, privateKey, mfaKeysStored, + openidDisplayName, ]; @override String get aliasedName => _alias ?? actualTableName; @@ -282,6 +295,15 @@ class $DefguardInstancesTable extends DefguardInstances } else if (isInserting) { context.missing(_mfaKeysStoredMeta); } + if (data.containsKey('openid_display_name')) { + context.handle( + _openidDisplayNameMeta, + openidDisplayName.isAcceptableOrUnknown( + data['openid_display_name']!, + _openidDisplayNameMeta, + ), + ); + } return context; } @@ -346,6 +368,10 @@ class $DefguardInstancesTable extends DefguardInstances DriftSqlType.bool, data['${effectivePrefix}mfa_keys_stored'], )!, + openidDisplayName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}openid_display_name'], + ), ); } @@ -373,6 +399,7 @@ class DefguardInstance extends DataClass final String pubKey; final String privateKey; final bool mfaKeysStored; + final String? openidDisplayName; const DefguardInstance({ required this.id, required this.name, @@ -387,6 +414,7 @@ class DefguardInstance extends DataClass required this.pubKey, required this.privateKey, required this.mfaKeysStored, + this.openidDisplayName, }); @override Map toColumns(bool nullToAbsent) { @@ -410,6 +438,9 @@ class DefguardInstance extends DataClass map['pub_key'] = Variable(pubKey); map['private_key'] = Variable(privateKey); map['mfa_keys_stored'] = Variable(mfaKeysStored); + if (!nullToAbsent || openidDisplayName != null) { + map['openid_display_name'] = Variable(openidDisplayName); + } return map; } @@ -428,6 +459,9 @@ class DefguardInstance extends DataClass pubKey: Value(pubKey), privateKey: Value(privateKey), mfaKeysStored: Value(mfaKeysStored), + openidDisplayName: openidDisplayName == null && nullToAbsent + ? const Value.absent() + : Value(openidDisplayName), ); } @@ -452,6 +486,9 @@ class DefguardInstance extends DataClass pubKey: serializer.fromJson(json['pubKey']), privateKey: serializer.fromJson(json['privateKey']), mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + openidDisplayName: serializer.fromJson( + json['openidDisplayName'], + ), ); } @override @@ -473,6 +510,7 @@ class DefguardInstance extends DataClass 'pubKey': serializer.toJson(pubKey), 'privateKey': serializer.toJson(privateKey), 'mfaKeysStored': serializer.toJson(mfaKeysStored), + 'openidDisplayName': serializer.toJson(openidDisplayName), }; } @@ -490,6 +528,7 @@ class DefguardInstance extends DataClass String? pubKey, String? privateKey, bool? mfaKeysStored, + Value openidDisplayName = const Value.absent(), }) => DefguardInstance( id: id ?? this.id, name: name ?? this.name, @@ -504,6 +543,9 @@ class DefguardInstance extends DataClass pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName.present + ? openidDisplayName.value + : this.openidDisplayName, ); DefguardInstance copyWithCompanion(DefguardInstancesCompanion data) { return DefguardInstance( @@ -530,6 +572,9 @@ class DefguardInstance extends DataClass mfaKeysStored: data.mfaKeysStored.present ? data.mfaKeysStored.value : this.mfaKeysStored, + openidDisplayName: data.openidDisplayName.present + ? data.openidDisplayName.value + : this.openidDisplayName, ); } @@ -548,7 +593,8 @@ class DefguardInstance extends DataClass ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') - ..write('mfaKeysStored: $mfaKeysStored') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') ..write(')')) .toString(); } @@ -568,6 +614,7 @@ class DefguardInstance extends DataClass pubKey, privateKey, mfaKeysStored, + openidDisplayName, ); @override bool operator ==(Object other) => @@ -585,7 +632,8 @@ class DefguardInstance extends DataClass other.enterpriseEnabled == this.enterpriseEnabled && other.pubKey == this.pubKey && other.privateKey == this.privateKey && - other.mfaKeysStored == this.mfaKeysStored); + other.mfaKeysStored == this.mfaKeysStored && + other.openidDisplayName == this.openidDisplayName); } class DefguardInstancesCompanion extends UpdateCompanion { @@ -602,6 +650,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { final Value pubKey; final Value privateKey; final Value mfaKeysStored; + final Value openidDisplayName; const DefguardInstancesCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), @@ -616,6 +665,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { this.pubKey = const Value.absent(), this.privateKey = const Value.absent(), this.mfaKeysStored = const Value.absent(), + this.openidDisplayName = const Value.absent(), }); DefguardInstancesCompanion.insert({ this.id = const Value.absent(), @@ -631,6 +681,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { required String pubKey, required String privateKey, required bool mfaKeysStored, + this.openidDisplayName = const Value.absent(), }) : name = Value(name), uuid = Value(uuid), url = Value(url), @@ -656,6 +707,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Expression? pubKey, Expression? privateKey, Expression? mfaKeysStored, + Expression? openidDisplayName, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -672,6 +724,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (pubKey != null) 'pub_key': pubKey, if (privateKey != null) 'private_key': privateKey, if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + if (openidDisplayName != null) 'openid_display_name': openidDisplayName, }); } @@ -689,6 +742,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Value? pubKey, Value? privateKey, Value? mfaKeysStored, + Value? openidDisplayName, }) { return DefguardInstancesCompanion( id: id ?? this.id, @@ -704,6 +758,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName ?? this.openidDisplayName, ); } @@ -753,6 +808,9 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (mfaKeysStored.present) { map['mfa_keys_stored'] = Variable(mfaKeysStored.value); } + if (openidDisplayName.present) { + map['openid_display_name'] = Variable(openidDisplayName.value); + } return map; } @@ -771,7 +829,8 @@ class DefguardInstancesCompanion extends UpdateCompanion { ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') - ..write('mfaKeysStored: $mfaKeysStored') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') ..write(')')) .toString(); } @@ -1645,6 +1704,7 @@ typedef $$DefguardInstancesTableCreateCompanionBuilder = required String pubKey, required String privateKey, required bool mfaKeysStored, + Value openidDisplayName, }); typedef $$DefguardInstancesTableUpdateCompanionBuilder = DefguardInstancesCompanion Function({ @@ -1661,6 +1721,7 @@ typedef $$DefguardInstancesTableUpdateCompanionBuilder = Value pubKey, Value privateKey, Value mfaKeysStored, + Value openidDisplayName, }); final class $$DefguardInstancesTableReferences @@ -1773,6 +1834,11 @@ class $$DefguardInstancesTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get openidDisplayName => $composableBuilder( + column: $table.openidDisplayName, + builder: (column) => ColumnFilters(column), + ); + Expression locationsRefs( Expression Function($$LocationsTableFilterComposer f) f, ) { @@ -1872,6 +1938,11 @@ class $$DefguardInstancesTableOrderingComposer column: $table.mfaKeysStored, builder: (column) => ColumnOrderings(column), ); + + ColumnOrderings get openidDisplayName => $composableBuilder( + column: $table.openidDisplayName, + builder: (column) => ColumnOrderings(column), + ); } class $$DefguardInstancesTableAnnotationComposer @@ -1933,6 +2004,11 @@ class $$DefguardInstancesTableAnnotationComposer builder: (column) => column, ); + GeneratedColumn get openidDisplayName => $composableBuilder( + column: $table.openidDisplayName, + builder: (column) => column, + ); + Expression locationsRefs( Expression Function($$LocationsTableAnnotationComposer a) f, ) { @@ -2006,6 +2082,7 @@ class $$DefguardInstancesTableTableManager Value pubKey = const Value.absent(), Value privateKey = const Value.absent(), Value mfaKeysStored = const Value.absent(), + Value openidDisplayName = const Value.absent(), }) => DefguardInstancesCompanion( id: id, name: name, @@ -2020,6 +2097,7 @@ class $$DefguardInstancesTableTableManager pubKey: pubKey, privateKey: privateKey, mfaKeysStored: mfaKeysStored, + openidDisplayName: openidDisplayName, ), createCompanionCallback: ({ @@ -2037,6 +2115,7 @@ class $$DefguardInstancesTableTableManager required String pubKey, required String privateKey, required bool mfaKeysStored, + Value openidDisplayName = const Value.absent(), }) => DefguardInstancesCompanion.insert( id: id, name: name, @@ -2051,6 +2130,7 @@ class $$DefguardInstancesTableTableManager pubKey: pubKey, privateKey: privateKey, mfaKeysStored: mfaKeysStored, + openidDisplayName: openidDisplayName, ), withReferenceMapper: (p0) => p0 .map( diff --git a/client/lib/data/db/database.steps.dart b/client/lib/data/db/database.steps.dart index c2c82037..237aeba7 100644 --- a/client/lib/data/db/database.steps.dart +++ b/client/lib/data/db/database.steps.dart @@ -312,8 +312,110 @@ i1.GeneratedColumn _column_23(String aliasedName) => true, type: i1.DriftSqlType.int, ); + +final class Schema3 extends i0.VersionedSchema { + Schema3({required super.database}) : super(version: 3); + @override + late final List entities = [ + defguardInstances, + locations, + ]; + late final Shape2 defguardInstances = Shape2( + source: i0.VersionedTable( + entityName: 'defguard_instances', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_24, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 locations = Shape1( + source: i0.VersionedTable( + entityName: 'locations', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_13, + _column_14, + _column_1, + _column_15, + _column_10, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape2 extends i0.VersionedTable { + Shape2({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get uuid => + columnsByName['uuid']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => + columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get deviceId => + columnsByName['device_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get proxyUrl => + columnsByName['proxy_url']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => + columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get poolingToken => + columnsByName['pooling_token']! as i1.GeneratedColumn; + i1.GeneratedColumn get clientTrafficPolicy => + columnsByName['client_traffic_policy']! as i1.GeneratedColumn; + i1.GeneratedColumn get enterpriseEnabled => + columnsByName['enterprise_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get privateKey => + columnsByName['private_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaKeysStored => + columnsByName['mfa_keys_stored']! as i1.GeneratedColumn; + i1.GeneratedColumn get openidDisplayName => + columnsByName['openid_display_name']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_24(String aliasedName) => + i1.GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -322,6 +424,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from1To2(migrator, schema); return 2; + case 2: + final schema = Schema3(database: database); + final migrator = i1.Migrator(database, schema); + await from2To3(migrator, schema); + return 3; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -330,6 +437,7 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, }) => i0.VersionedSchema.stepByStepHelper( - step: migrationSteps(from1To2: from1To2), + step: migrationSteps(from1To2: from1To2, from2To3: from2To3), ); diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index 916ca791..87e5d080 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -253,6 +253,7 @@ class InstanceInfo { final bool enterpriseEnabled; final bool disableAllTraffic; final ClientTrafficPolicy? clientTrafficPolicy; + final String? openidDisplayName; const InstanceInfo({ required this.id, @@ -262,9 +263,9 @@ class InstanceInfo { required this.username, required this.enterpriseEnabled, // deprecated, use clientTrafficPolicy instead - @Deprecated('1.6') - required this.disableAllTraffic, + @Deprecated('1.6') required this.disableAllTraffic, required this.clientTrafficPolicy, + this.openidDisplayName, }); factory InstanceInfo.fromJson(Map json) => @@ -279,7 +280,8 @@ class InstanceInfo { proxyUrl == other.proxyUrl && username == other.username && enterpriseEnabled == other.enterpriseEnabled && - getPolicy() == other.clientTrafficPolicy; + getPolicy() == other.clientTrafficPolicy && + openidDisplayName == other.openidDisplayName; } DefguardInstancesCompanion toCompanion({DefguardInstance? instance}) { @@ -296,14 +298,16 @@ class InstanceInfo { enterpriseEnabled: d.Value(enterpriseEnabled), clientTrafficPolicy: d.Value(getPolicy()), uuid: d.Value(id), + openidDisplayName: d.Value(openidDisplayName), ); } /// Retrieves `ClientTrafficPolicy` while ensuring backwards compatibility ClientTrafficPolicy getPolicy() { - return clientTrafficPolicy ?? (disableAllTraffic - ? ClientTrafficPolicy.disableAllTraffic - : ClientTrafficPolicy.none); + return clientTrafficPolicy ?? + (disableAllTraffic + ? ClientTrafficPolicy.disableAllTraffic + : ClientTrafficPolicy.none); } } diff --git a/client/lib/data/proxy/enrollment.g.dart b/client/lib/data/proxy/enrollment.g.dart index df153cef..5f5cf4b3 100644 --- a/client/lib/data/proxy/enrollment.g.dart +++ b/client/lib/data/proxy/enrollment.g.dart @@ -393,6 +393,10 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'client_traffic_policy', (v) => $enumDecodeNullable(_$ClientTrafficPolicyEnumMap, v), ), + openidDisplayName: $checkedConvert( + 'openid_display_name', + (v) => v as String?, + ), ); return val; }, @@ -401,6 +405,7 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', 'clientTrafficPolicy': 'client_traffic_policy', + 'openidDisplayName': 'openid_display_name', }, ); @@ -413,6 +418,7 @@ const _$InstanceInfoFieldMap = { 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', 'clientTrafficPolicy': 'client_traffic_policy', + 'openidDisplayName': 'openid_display_name', }; Map _$InstanceInfoToJson(InstanceInfo instance) => @@ -426,6 +432,7 @@ Map _$InstanceInfoToJson(InstanceInfo instance) => 'disable_all_traffic': instance.disableAllTraffic, 'client_traffic_policy': _$ClientTrafficPolicyEnumMap[instance.clientTrafficPolicy], + 'openid_display_name': instance.openidDisplayName, }; const _$ClientTrafficPolicyEnumMap = { diff --git a/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart b/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart index 2eb337ab..7d4cc692 100644 --- a/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart +++ b/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart @@ -17,18 +17,28 @@ import '../../../open/services/snackbar_service.dart'; class OpenIdMfaScreenData { final String proxyUrl; final String token; + final String? openidDisplayName; - const OpenIdMfaScreenData({required this.proxyUrl, required this.token}); + const OpenIdMfaScreenData({ + required this.proxyUrl, + required this.token, + this.openidDisplayName, + }); } final String _title = "Two-factor authentication"; -final String _mfaMsg1 = - "In order to connect to VPN please login with your OpenID provider. To do so, please click \"Authenticate with OpenId\""; +String _mfaMsg1(String? providerName) { + final name = providerName ?? 'OpenID'; + return "In order to connect to VPN please login with $name. To do so, please click \"Authenticate with $name\" button below"; +} -final String _mfaMsg2 = - "This will open a new window in your web browser and automatically redirect you to your OpenID provider login page. After authenticating please get back here"; +String _mfaMsg2(String? providerName) { + final name = providerName ?? 'OpenID'; + return "This will open a new window in your Web Browser and automatically redirect you to the $name login page. After authenticating with $name please get back here"; +} -final String _authenticateMsg = "Authenticate with OpenID"; +String _authenticateMsg(String? providerName) => + 'Authenticate with ${providerName ?? 'OpenID'}'; class OpenIdMfaScreen extends HookConsumerWidget { final OpenIdMfaScreenData screenData; @@ -67,17 +77,17 @@ class OpenIdMfaScreen extends HookConsumerWidget { ), Center(child: DgIconOpenidOpen(size: 128)), Text( - _mfaMsg1, + _mfaMsg1(screenData.openidDisplayName), style: DgText.modal1.copyWith(color: DgColor.textBodySecondary), textAlign: TextAlign.center, ), Text( - _mfaMsg2, + _mfaMsg2(screenData.openidDisplayName), style: DgText.modal1.copyWith(color: DgColor.textBodySecondary), textAlign: TextAlign.center, ), DgButton( - text: _authenticateMsg, + text: _authenticateMsg(screenData.openidDisplayName), variant: DgButtonVariant.primary, size: DgButtonSize.big, width: double.infinity, diff --git a/client/lib/open/screens/add_instance/screens/name_device_screen.dart b/client/lib/open/screens/add_instance/screens/name_device_screen.dart index ba583b7a..6e25082e 100644 --- a/client/lib/open/screens/add_instance/screens/name_device_screen.dart +++ b/client/lib/open/screens/add_instance/screens/name_device_screen.dart @@ -64,6 +64,9 @@ class NameDeviceScreen extends HookConsumerWidget { username: createResponse.instance.username, poolingToken: createResponse.token, mfaKeysStored: false, + openidDisplayName: drift.Value( + createResponse.instance.openidDisplayName, + ), ), mode: drift.InsertMode.insertOrFail, ); @@ -101,7 +104,7 @@ class NameDeviceScreen extends HookConsumerWidget { suggestedName = ""; } nameController.text = suggestedName; - } catch(e) { + } catch (e) { talker.error("Failed to get suggested device name! Reason: $e"); } } diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index a52a774f..b44cc4a8 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -38,7 +38,8 @@ class TunnelService { if (instance.clientTrafficPolicy == ClientTrafficPolicy.disableAllTraffic) { // instance enforces predefined traffic trafficMethod = RoutingMethod.predefined; - } else if (instance.clientTrafficPolicy == ClientTrafficPolicy.forceAllTraffic) { + } else if (instance.clientTrafficPolicy == + ClientTrafficPolicy.forceAllTraffic) { // instance enforces all traffic trafficMethod = RoutingMethod.all; } else { @@ -111,6 +112,7 @@ class TunnelService { payload: payload, method: mfaMethod, secureStorageKey: instance.secureStorageKey, + openidDisplayName: instance.openidDisplayName, ); if (presharedKey == null) { // user dismissed the dialog @@ -139,6 +141,7 @@ class TunnelService { required PluginConnectPayload payload, required MfaMethod method, String? secureStorageKey, + String? openidDisplayName, }) async { // prepare messenger to avoid "context use across async gaps" final messenger = ScaffoldMessenger.of(navigator.context); @@ -157,6 +160,7 @@ class TunnelService { token: startMfaResponse.token, proxyUrl: proxyUrl, method: method, + openidDisplayName: openidDisplayName, ); } if (method == MfaMethod.biometric) { @@ -227,11 +231,16 @@ class TunnelService { required String token, required String proxyUrl, required MfaMethod method, + String? openidDisplayName, }) async { final presharedKey = await Navigator.of(navigator.context).push( MaterialPageRoute( builder: (context) => OpenIdMfaScreen( - screenData: OpenIdMfaScreenData(proxyUrl: proxyUrl, token: token), + screenData: OpenIdMfaScreenData( + proxyUrl: proxyUrl, + token: token, + openidDisplayName: openidDisplayName, + ), ), ), ); diff --git a/client/test/drift/defguard/generated/schema.dart b/client/test/drift/defguard/generated/schema.dart index b2b7404b..209e70d7 100644 --- a/client/test/drift/defguard/generated/schema.dart +++ b/client/test/drift/defguard/generated/schema.dart @@ -5,6 +5,7 @@ import 'package:drift/drift.dart'; import 'package:drift/internal/migrations.dart'; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; +import 'schema_v3.dart' as v3; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -14,10 +15,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v1.DatabaseAtV1(db); case 2: return v2.DatabaseAtV2(db); + case 3: + return v3.DatabaseAtV3(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2]; + static const versions = const [1, 2, 3]; } diff --git a/client/test/drift/defguard/generated/schema_v3.dart b/client/test/drift/defguard/generated/schema_v3.dart new file mode 100644 index 00000000..54f693ba --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v3.dart @@ -0,0 +1,1321 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn clientTrafficPolicy = GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + late final GeneratedColumn openidDisplayName = + GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + clientTrafficPolicy: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + openidDisplayName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}openid_display_name'], + ), + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final int clientTrafficPolicy; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + final String? openidDisplayName; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.clientTrafficPolicy, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + this.openidDisplayName, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['client_traffic_policy'] = Variable(clientTrafficPolicy); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + if (!nullToAbsent || openidDisplayName != null) { + map['openid_display_name'] = Variable(openidDisplayName); + } + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + clientTrafficPolicy: Value(clientTrafficPolicy), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + openidDisplayName: openidDisplayName == null && nullToAbsent + ? const Value.absent() + : Value(openidDisplayName), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + clientTrafficPolicy: serializer.fromJson( + json['clientTrafficPolicy'], + ), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + openidDisplayName: serializer.fromJson( + json['openidDisplayName'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'clientTrafficPolicy': serializer.toJson(clientTrafficPolicy), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + 'openidDisplayName': serializer.toJson(openidDisplayName), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + int? clientTrafficPolicy, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + Value openidDisplayName = const Value.absent(), + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName.present + ? openidDisplayName.value + : this.openidDisplayName, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + openidDisplayName: data.openidDisplayName.present + ? data.openidDisplayName.value + : this.openidDisplayName, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.clientTrafficPolicy == this.clientTrafficPolicy && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored && + other.openidDisplayName == this.openidDisplayName); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value clientTrafficPolicy; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + final Value openidDisplayName; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + this.openidDisplayName = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + this.clientTrafficPolicy = const Value.absent(), + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + this.openidDisplayName = const Value.absent(), + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? clientTrafficPolicy, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + Expression? openidDisplayName, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + if (openidDisplayName != null) 'openid_display_name': openidDisplayName, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? clientTrafficPolicy, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + Value? openidDisplayName, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName ?? this.openidDisplayName, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable(clientTrafficPolicy.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + if (openidDisplayName.present) { + map['openid_display_name'] = Variable(openidDisplayName.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV3 extends GeneratedDatabase { + DatabaseAtV3(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 3; +} From d8fd1252a94b1123948b8a7755386351e5f6f656 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 15 May 2026 13:23:36 +0200 Subject: [PATCH 14/65] bump version (#186) --- client/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- client/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index d8b5fda6..97f5ddc9 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -644,7 +644,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -698,7 +698,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -749,7 +749,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 5c8feae3..54384945 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.2+1 +version: 1.6.3+1 environment: sdk: ^3.8.1 From 68ac1ea3a923a623d1f9d9e6f633ca1e16feb26a Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 22 May 2026 10:46:36 +0200 Subject: [PATCH 15/65] Sync VPNExtension with Client (#187) --- .github/workflows/build.yaml | 6 +- .github/workflows/lint-and-test.yaml | 4 +- client/ios/Podfile.lock | 30 +- client/ios/VPNExtension/Adapter.swift | 294 +++++++++++------- .../ios/VPNExtension/Decodabe+Encodable.swift | 6 +- client/ios/VPNExtension/Endpoint.swift | 38 ++- client/ios/VPNExtension/FileLogger.swift | 251 +++++++++++++++ .../VPNExtension/InterfaceConfiguration.swift | 15 - client/ios/VPNExtension/IpAddrMask.swift | 44 +-- .../VPNExtension/PacketTunnelProvider.swift | 121 +++---- client/ios/VPNExtension/Peer.swift | 24 +- client/ios/VPNExtension/Stats.swift | 18 ++ .../VPNExtension/TunnelConfiguration.swift | 84 ++--- client/ios/boringtun | 2 +- client/pubspec.lock | 96 +++--- 15 files changed, 696 insertions(+), 337 deletions(-) create mode 100644 client/ios/VPNExtension/FileLogger.swift delete mode 100644 client/ios/VPNExtension/InterfaceConfiguration.swift create mode 100644 client/ios/VPNExtension/Stats.swift diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0c65191e..cddbe409 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,7 +29,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.9 + flutter-version: 3.38.10 - name: Use homebrew ruby run: | @@ -89,7 +89,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.10 - name: Accept licenses run: yes | flutter doctor --android-licenses @@ -159,7 +159,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.10 - name: Install Android SDK components run: | diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 2b719f3b..31bb7ce6 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -47,7 +47,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.10 - name: get deps run: flutter pub get @@ -73,7 +73,7 @@ jobs: # uses: subosito/flutter-action@v2 # with: # channel: stable - # flutter-version: 3.38.7 + # flutter-version: 3.38.10 # - name: get deps # run: flutter pub get diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index c7747bb2..ff20001b 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -113,23 +113,23 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a - cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 + cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 + mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 + sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa + wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/ios/VPNExtension/Adapter.swift b/client/ios/VPNExtension/Adapter.swift index 713fed9c..bd365003 100644 --- a/client/ios/VPNExtension/Adapter.swift +++ b/client/ios/VPNExtension/Adapter.swift @@ -1,7 +1,6 @@ import Foundation import Network import NetworkExtension -import os /// State of Adapter. enum State { @@ -13,7 +12,7 @@ enum State { case dormant } -final class Adapter /*: Sendable*/ { +@preconcurrency final class Adapter /*: Sendable*/ { /// Packet tunnel provider. private weak var packetTunnelProvider: NEPacketTunnelProvider? /// BortingTun tunnel @@ -25,17 +24,26 @@ final class Adapter /*: Sendable*/ { /// Network routes monitor. private var networkMonitor: NWPathMonitor? /// Keep alive timer - private var keepAliveTimer: Timer? - /// Logging - private lazy var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Adapter") + private var keepAliveTimer: DispatchSourceTimer? + /// Unified logger (writes to both system log and file) + private let log = Log(category: "Adapter") /// Adapter state. private var state: State = .stopped - private var reconnectOnExpiry: Bool = false + /// Serialize tunnel I/O and connection state changes off the main queue. + private let ioQueue = DispatchQueue(label: "net.defguard.VPNExtension.adapter") + private let ioQueueKey = DispatchSpecificKey() + + /// For statistics returned to Rust code. + var locationId: UInt64? + var tunnelId: UInt64? + + private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() /// Designated initializer. /// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored init(with packetTunnelProvider: NEPacketTunnelProvider) { self.packetTunnelProvider = packetTunnelProvider + self.ioQueue.setSpecific(key: ioQueueKey, value: ()) } deinit { @@ -43,8 +51,49 @@ final class Adapter /*: Sendable*/ { } func start(tunnelConfiguration: TunnelConfiguration) throws { - if let _ = tunnel { - logger.info("Cleaning exiting Tunnel") + try syncOnQueue { + try startOnQueue(tunnelConfiguration: tunnelConfiguration) + } + } + + func stop() { + syncOnQueue { + stopOnQueue() + } + } + + // Obtain tunnel statistics. + func stats() -> Stats? { + syncOnQueue { + guard let stats = tunnel?.stats() else { return nil } + return Stats( + txBytes: stats.txBytes, + rxBytes: stats.rxBytes, + lastHandshake: stats.lastHandshake, + locationId: locationId, + tunnelId: tunnelId + ) + } + } + + private func syncOnQueue(_ work: () throws -> T) rethrows -> T { + if DispatchQueue.getSpecific(key: ioQueueKey) == nil { + return try ioQueue.sync { + try work() + } + } + return try work() + } + + private func startOnQueue(tunnelConfiguration: TunnelConfiguration) throws { + guard case .stopped = self.state else { + log.error("Invalid state - cannot start tunnel") + // TODO: throw invalid state + return + } + + if tunnel != nil { + log.info("Cleaning existing Tunnel") tunnel = nil connection = nil } @@ -53,108 +102,102 @@ final class Adapter /*: Sendable*/ { networkMonitor.pathUpdateHandler = { [weak self] path in self?.networkPathUpdate(path: path) } - networkMonitor.start(queue: .main) + networkMonitor.start(queue: ioQueue) self.networkMonitor = networkMonitor - logger.info("Initializing Tunnel") + log.info("Initializing Tunnel") tunnel = try Tunnel.init( - privateKey: tunnelConfiguration.interface.privateKey, + privateKey: tunnelConfiguration.privateKey, serverPublicKey: tunnelConfiguration.peers[0].publicKey, presharedKey: tunnelConfiguration.peers[0].preSharedKey, keepAlive: tunnelConfiguration.peers[0].persistentKeepAlive, index: 0 ) + locationId = tunnelConfiguration.locationId + tunnelId = tunnelConfiguration.tunnelId - if tunnelConfiguration.peers[0].preSharedKey != nil { - logger.info("Using pre-shared key, the tunnel won't be re-established on expiry") - reconnectOnExpiry = false - } else { - logger.info("No pre-shared key, the tunnel will be re-established on expiry") - reconnectOnExpiry = true - } - - logger.info("Connecting to endpoint") + log.info( + "Connecting to endpoint (locationId: \(tunnelConfiguration.locationId ?? 0), tunnelId: \(tunnelConfiguration.tunnelId ?? 0))" + ) guard let endpoint = tunnelConfiguration.peers[0].endpoint else { - logger.error("Endpoint is nil") + log.error("Endpoint is nil, cannot connect") return } self.endpoint = endpoint.asNWEndpoint() initEndpoint() - logger.info("Sniffing packets") + log.info("Starting to sniff packets") readPackets() state = .running + log.info("Tunnel started successfully") } - func stop() { - logger.info("Stopping Adapter") + private func stopOnQueue() { + log.info("Stopping Adapter") connection?.cancel() connection = nil tunnel = nil - keepAliveTimer?.invalidate() + keepAliveTimer?.cancel() keepAliveTimer = nil // Cancel network monitor networkMonitor?.cancel() networkMonitor = nil + state = .stopped - logger.info("Tunnel stopped") + log.info("Tunnel stopped") + log.flush() } private func handleTunnelResult(_ result: TunnelResult) { + var tunnelPackets = [NEPacket]() + handleTunnelResult(result, tunnelPackets: &tunnelPackets) + flushTunnelPackets(tunnelPackets) + } + + private func handleTunnelResult(_ result: TunnelResult, tunnelPackets: inout [NEPacket]) { switch result { - case .done: - // Nothing to do. - break - case .err(let error): - logger.error("Tunnel error \(error, privacy: .public)") - switch error { - case .InvalidAeadTag: - logger.error("Invalid pre-shared key; stopping tunnel") - // The correct way is to call the packet tunnel provider, if there is one. - if let provider = packetTunnelProvider { - provider.cancelTunnelWithError(error) - } else { - stop() - } - case .ConnectionExpired: - packetTunnelProvider?.reasserting = true - if self.reconnectOnExpiry { - logger.error("Connecion has expired; re-connecting") - initEndpoint() - logger.info("Finished re-connecting") - } else { - logger.error("Connection has expired; stopping tunnel") - let defaults = UserDefaults(suiteName: suiteName) - defaults?.set( - TunnelStopError.mfaSessionExpired.rawValue - , forKey: "lastTunnelError") - if let provider = packetTunnelProvider { - provider.cancelTunnelWithError(error) - } else { - stop() - } - } - packetTunnelProvider?.reasserting = false - default: - break + case .done: + // Nothing to do. + break + case .err(let error): + log.error("Tunnel error: \(error)") + switch error { + case .InvalidAeadTag: + log.error("Invalid pre-shared key; stopping tunnel") + // The correct way is to call the packet tunnel provider, if there is one. + if let provider = packetTunnelProvider { + provider.cancelTunnelWithError(error) + } else { + stop() } - case .writeToNetwork(let data): - sendToEndpoint(data: data) - case .writeToTunnelV4(let data): - packetTunnelProvider?.packetFlow.writePacketObjects([ - NEPacket(data: data,protocolFamily: sa_family_t(AF_INET))]) - case .writeToTunnelV6(let data): - packetTunnelProvider?.packetFlow.writePacketObjects([ - NEPacket(data: data, protocolFamily: sa_family_t(AF_INET6))]) + case .ConnectionExpired: + log.warning("Connection has expired; re-connecting") + packetTunnelProvider?.reasserting = true + initEndpoint() + packetTunnelProvider?.reasserting = false + default: + break + } + case .writeToNetwork(let data): + sendToEndpoint(data: data) + case .writeToTunnelV4(let data): + tunnelPackets.append(NEPacket(data: data, protocolFamily: sa_family_t(AF_INET))) + case .writeToTunnelV6(let data): + tunnelPackets.append(NEPacket(data: data, protocolFamily: sa_family_t(AF_INET6))) } } + private func flushTunnelPackets(_ tunnelPackets: [NEPacket]) { + guard !tunnelPackets.isEmpty else { return } + packetTunnelProvider?.packetFlow.writePacketObjects(tunnelPackets) + } + /// Initialise UDP connection to endpoint. private func initEndpoint() { guard let endpoint = endpoint else { return } - logger.info("Init Endpoint") + log.info("Initializing endpoint connection to: \(endpoint)") // Cancel previous connection connection?.cancel() connection = nil @@ -166,43 +209,53 @@ final class Adapter /*: Sendable*/ { self?.endpointStateChange(state: state) } - connection.start(queue: .main) + connection.start(queue: ioQueue) self.connection = connection } /// Setup UDP connection to endpoint. This method should be called when UDP connection is ready to send and receive. private func setupEndpoint() { - logger.info("Setup endpoint") + log.info("Setting up endpoint") // Send initial handshake packet if let tunnel = self.tunnel { + log.info("Sending initial handshake") handleTunnelResult(tunnel.forceHandshake()) } - logger.info("Receiving UDP from endpoint") + log.info("Starting UDP receive loop") + log.debug("NWConnection path: \(String(describing: self.connection?.currentPath))") receive() - // Use Timer to send keep-alive packets. - keepAliveTimer?.invalidate() - logger.info("Creating keep-alive timer") - let timer = Timer(timeInterval: 0.25, repeats: true) { [weak self] timer in + // Use a dispatch timer to avoid bouncing keep-alives through the main run loop. + keepAliveTimer?.cancel() + log.info("Creating keep-alive timer") + let timer = DispatchSource.makeTimerSource(queue: ioQueue) + timer.schedule( + deadline: .now() + .milliseconds(250), + repeating: .milliseconds(250), + leeway: .milliseconds(25) + ) + timer.setEventHandler { [weak self] in guard let self = self, let tunnel = self.tunnel else { return } self.handleTunnelResult(tunnel.tick()) } keepAliveTimer = timer - RunLoop.main.add(timer, forMode: .common) + timer.resume() } /// Send packets to UDP endpoint. private func sendToEndpoint(data: Data) { guard let connection = connection else { return } if connection.state == .ready { - connection.send(content: data, completion: .contentProcessed { error in - if let error = error { - self.logger.error("UDP connection send error: \(error, privacy: .public)") - } - }) + connection.send( + content: data, + completion: .contentProcessed { [weak self] error in + if let error = error { + self?.log.error("UDP connection send error: \(error)") + } + }) } else { - logger.warning("UDP connection not ready to send") + log.warning("UDP connection not ready to send") } } @@ -211,60 +264,89 @@ final class Adapter /*: Sendable*/ { connection?.receiveMessage { [weak self] data, context, isComplete, error in guard let self = self else { return } if let data = data, let tunnel = self.tunnel { - self.handleTunnelResult(tunnel.read(src: data)) + autoreleasepool { + self.handleTunnelResult(tunnel.read(src: data)) + } } if error == nil { // continue receiving self.receive() + } else { + self.log.error("receive() error: \(String(describing: error))") } } } /// Read tunnel packets. private func readPackets() { + // Packets received to the tunnel's virtual interface. + packetTunnelProvider?.packetFlow.readPacketObjects { [weak self] packets in + guard let self = self else { return } + + self.ioQueue.async { + self.processTunnelPackets(packets) + + // continue reading + self.readPackets() + } + } + } + + private func processTunnelPackets(_ packets: [NEPacket]) { guard let tunnel = self.tunnel else { return } - // Packets received to the tunnel's virtual interface. - packetTunnelProvider?.packetFlow.readPacketObjects { packets in - for packet in packets { - self.handleTunnelResult(tunnel.write(src: packet.data)) + var tunnelPackets = [NEPacket]() + tunnelPackets.reserveCapacity(packets.count) + + for packet in packets { + autoreleasepool { + self.handleTunnelResult(tunnel.write(src: packet.data), tunnelPackets: &tunnelPackets) } - // continue reading - self.readPackets() } + + flushTunnelPackets(tunnelPackets) } /// Handle UDP connection state changes. private func endpointStateChange(state: NWConnection.State) { - logger.debug("UDP connection state: \(String(describing: state), privacy: .public)") + log.debug("UDP connection state changed: \(state)") switch state { - case .ready: - setupEndpoint() - case .failed(let error): - logger.error("Failed to establish endpoint connection: \(error)") - // The correct way is to call the packet tunnel provider, if there is one. - if let provider = packetTunnelProvider { - provider.cancelTunnelWithError(error) - } else { - stop() - } - default: - break + case .ready: + setupEndpoint() + //case .waiting(let error): + // switch error { + // case .posix(_): + // connection?.restart() + // default: + // self.stop() + // } + case .failed(let error): + log.error("Failed to establish endpoint connection: \(error)") + // The correct way is to call the packet tunnel provider, if there is one. + if let provider = packetTunnelProvider { + provider.cancelTunnelWithError(error) + } else { + stop() + } + default: + break } } /// Handle network path updates. private func networkPathUpdate(path: Network.NWPath) { + log.debug( + "Network path update - status: \(path.status), interfaces: \(path.availableInterfaces)") if path.status == .unsatisfied { if state == .running { - logger.warning("Unsatisfied network path: going dormant") + log.warning("Unsatisfied network path: going dormant") connection?.cancel() connection = nil state = .dormant } } else { if state == .dormant { - logger.warning("Satisfied network path: going running") + log.warning("Satisfied network path: going running") initEndpoint() state = .running } diff --git a/client/ios/VPNExtension/Decodabe+Encodable.swift b/client/ios/VPNExtension/Decodabe+Encodable.swift index bf01a541..d3f8a8f0 100644 --- a/client/ios/VPNExtension/Decodabe+Encodable.swift +++ b/client/ios/VPNExtension/Decodabe+Encodable.swift @@ -2,7 +2,7 @@ import Foundation extension Decodable { static func from(dictionary: [String: Any]) throws -> Self { - let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) + let data = try JSONSerialization.data(withJSONObject: dictionary) let decoder = JSONDecoder() return try decoder.decode(Self.self, from: data) } @@ -13,7 +13,9 @@ extension Encodable { let data = try JSONEncoder().encode(self) let jsonObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) guard let dictionary = jsonObject as? [String: Any] else { - throw NSError(domain: "EncodingError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to convert to dictionary"]) + throw NSError( + domain: "EncodingError", code: 0, + userInfo: [NSLocalizedDescriptionKey: "Failed to convert to dictionary"]) } return dictionary } diff --git a/client/ios/VPNExtension/Endpoint.swift b/client/ios/VPNExtension/Endpoint.swift index 790aee30..64f50a40 100644 --- a/client/ios/VPNExtension/Endpoint.swift +++ b/client/ios/VPNExtension/Endpoint.swift @@ -1,3 +1,4 @@ +import Foundation import Network struct Endpoint: Codable, CustomStringConvertible { @@ -15,9 +16,11 @@ struct Endpoint: Codable, CustomStringConvertible { var endpointHost = trimmedEndpoint // Extract host, supporting IPv4, IPv6, and domains - if trimmedEndpoint.hasPrefix("[") { // IPv6 with port, e.g. [fd00::1]:51820 + if trimmedEndpoint.hasPrefix("[") { // IPv6 with port, e.g. [fd00::1]:51820 if let closing = trimmedEndpoint.firstIndex(of: "]") { - endpointHost = String(trimmedEndpoint[trimmedEndpoint.index(after: trimmedEndpoint.startIndex).. String { + "\(host):\(port)" } + // Encode to a single string "host:port", to smoothly encode into JSON. func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("\(host)", forKey: .host) - try container.encode(port.rawValue, forKey: .port) + var container = encoder.singleValueContainer() + try container.encode(self.toString()) } + // Decode from a single string "host:port", to smoothly decode from JSON. init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - - host = try NWEndpoint.Host(values.decode(String.self, forKey: .host)) - port = try NWEndpoint.Port(rawValue: values.decode(UInt16.self, forKey: .port)) ?? 0 + let container = try decoder.singleValueContainer() + let value = try container.decode(String.self) + guard let endpoint = Endpoint(from: value) else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Not in host:port format") + ) + } + self = endpoint } func asNWEndpoint() -> NWEndpoint { diff --git a/client/ios/VPNExtension/FileLogger.swift b/client/ios/VPNExtension/FileLogger.swift new file mode 100644 index 00000000..71d02972 --- /dev/null +++ b/client/ios/VPNExtension/FileLogger.swift @@ -0,0 +1,251 @@ +import Foundation +import os + +/// Log levels +enum LogLevel: String { + case debug = "DEBUG" + case info = "INFO" + case warning = "WARN" + case error = "ERROR" + + var osLogType: OSLogType { + switch self { + case .debug: return .debug + case .info: return .info + case .warning: return .default + case .error: return .error + } + } +} + +/// Logger that writes to both system log (os.Logger) and file. +/// Use this instead of os.Logger directly to get dual logging with a single call. +final class Log { + /// The category for this logger instance (usually class name), e.g. "PacketTunnelProvider" + let category: String + private let systemLogger: Logger +#if os(macOS) + private let fileLogger = FileLogger.shared +#endif + + init(category: String) { + self.category = category + self.systemLogger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "net.defguard.VPNExtension", + category: category + ) + } + + func debug(_ message: String) { + systemLogger.debug("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .debug, message: message, category: category) +#endif + } + + func info(_ message: String) { + systemLogger.info("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .info, message: message, category: category) +#endif + } + + func warning(_ message: String) { + systemLogger.warning("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .warning, message: message, category: category) +#endif + } + + func error(_ message: String) { + systemLogger.error("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .error, message: message, category: category) +#endif + } + + func flush() { +#if os(macOS) + fileLogger.flush() +#endif + } +} + +#if os(macOS) +/// A file-based logger that writes to an App Group shared container. +/// This allows the main rust app to read logs from the network extension. +/// Use the `Log` class instead of this directly for unified logging. +final class FileLogger { + static let shared = FileLogger() + static let appGroupIdentifier = "group.net.defguard" + private let logFileName = "vpn-extension.log" + private let maxLogFileSize: UInt64 = 5 * 1024 * 1024 // 5 MB + private let maxBackupFiles = 3 + private let flushInterval = 5 // Flush every N log entries + private var fileHandle: FileHandle? + private var logFileURL: URL? + private var unflushedCount = 0 + + private let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(identifier: "UTC") + return formatter + }() + + private let queue = DispatchQueue(label: "net.defguard.VPNExtension.filelogger") + + private let internalLogger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "net.defguard.VPNExtension", + category: "FileLogger") + + private init() { + setupLogFile() + } + + deinit { + closeLogFile() + } + + private func setupLogFile() { + guard + let containerURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: Self.appGroupIdentifier) + else { + internalLogger.error( + "Failed to get App Group container URL for \(Self.appGroupIdentifier)") + return + } + + let logsDirectory = containerURL.appendingPathComponent("Logs", isDirectory: true) + + do { + try FileManager.default.createDirectory( + at: logsDirectory, withIntermediateDirectories: true, attributes: nil) + } catch { + internalLogger.error("Failed to create Logs directory: \(error.localizedDescription)") + return + } + + logFileURL = logsDirectory.appendingPathComponent(logFileName) + + guard let logFileURL = logFileURL else { return } + + if !FileManager.default.fileExists(atPath: logFileURL.path) { + FileManager.default.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + } + + do { + fileHandle = try FileHandle(forWritingTo: logFileURL) + fileHandle?.seekToEndOfFile() + + let startupMessage = + "# VPN Extension Log Started at \(dateFormatter.string(from: Date()))\n" + if let data = startupMessage.data(using: .utf8) { + fileHandle?.write(data) + } + } catch { + internalLogger.error( + "Failed to open log file for writing: \(error.localizedDescription)") + } + + internalLogger.info("FileLogger initialized at: \(logFileURL.path)") + } + + private func closeLogFile() { + queue.sync { + try? fileHandle?.synchronize() + try? fileHandle?.close() + fileHandle = nil + } + } + + /// Rotate log files if the current one exceeds the maximum size + private func rotateLogFilesIfNeeded() { + guard let logFileURL = logFileURL else { return } + + do { + let attributes = try FileManager.default.attributesOfItem(atPath: logFileURL.path) + if let fileSize = attributes[.size] as? UInt64, fileSize >= maxLogFileSize { + rotateLogFiles() + } + } catch { + } + } + + private func rotateLogFiles() { + guard let logFileURL = logFileURL else { return } + + try? fileHandle?.synchronize() + try? fileHandle?.close() + fileHandle = nil + + let fileManager = FileManager.default + let directory = logFileURL.deletingLastPathComponent() + let baseName = logFileURL.deletingPathExtension().lastPathComponent + let ext = logFileURL.pathExtension + + // Remove oldest backup if it exists + let oldestBackup = directory.appendingPathComponent("\(baseName).\(maxBackupFiles).\(ext)") + try? fileManager.removeItem(at: oldestBackup) + + for i in stride(from: maxBackupFiles - 1, through: 1, by: -1) { + let current = directory.appendingPathComponent("\(baseName).\(i).\(ext)") + let next = directory.appendingPathComponent("\(baseName).\(i + 1).\(ext)") + try? fileManager.moveItem(at: current, to: next) + } + + let firstBackup = directory.appendingPathComponent("\(baseName).1.\(ext)") + try? fileManager.moveItem(at: logFileURL, to: firstBackup) + + fileManager.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + + do { + fileHandle = try FileHandle(forWritingTo: logFileURL) + fileHandle?.seekToEndOfFile() + } catch { + internalLogger.error( + "Failed to reopen log file after rotation: \(error.localizedDescription)") + } + } + + /// Write a log message to the file + /// - level: Log level (debug, info, warning, error) + /// - message: The message to log + /// - category: Optional category/subsystem + func log(level: LogLevel, message: String, category: String? = nil) { + queue.async { [weak self] in + guard let self = self, let fileHandle = self.fileHandle else { return } + + self.rotateLogFilesIfNeeded() + + let timestamp = self.dateFormatter.string(from: Date()) + let categoryStr = category.map { "[\($0)] " } ?? "" + let logLine = "\(timestamp) [\(level.rawValue)] \(categoryStr)\(message)\n" + + if let data = logLine.data(using: .utf8) { + fileHandle.write(data) + self.unflushedCount += 1 + + // Flush for important messages or periodically + if level == .error || level == .warning || self.unflushedCount >= self.flushInterval + { + try? fileHandle.synchronize() + self.unflushedCount = 0 + } + } + } + } + + func flush() { + queue.sync { + try? fileHandle?.synchronize() + } + } + + var logFilePath: String? { + return logFileURL?.path + } +} +#endif diff --git a/client/ios/VPNExtension/InterfaceConfiguration.swift b/client/ios/VPNExtension/InterfaceConfiguration.swift deleted file mode 100644 index c73f539a..00000000 --- a/client/ios/VPNExtension/InterfaceConfiguration.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -import NetworkExtension - -final class InterfaceConfiguration: Codable { - var privateKey: String - var addresses: [IpAddrMask] = [] - var listenPort: UInt16? - var mtu: UInt32? - var dns: [String] = [] - var dnsSearch: [String] = [] - - init(privateKey: String) { - self.privateKey = privateKey - } -} diff --git a/client/ios/VPNExtension/IpAddrMask.swift b/client/ios/VPNExtension/IpAddrMask.swift index 32f972d1..fefd8459 100644 --- a/client/ios/VPNExtension/IpAddrMask.swift +++ b/client/ios/VPNExtension/IpAddrMask.swift @@ -44,7 +44,7 @@ struct IpAddrMask: Codable, Equatable { /// Conform to `Encodable`. func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(address.rawValue, forKey: .address) + try container.encode("\(address)", forKey: .address) try container.encode(cidr, forKey: .cidr) } @@ -52,39 +52,21 @@ struct IpAddrMask: Codable, Equatable { init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - let address_data = try values.decode(Data.self, forKey: .address) - switch address_data.count { - case 4: - guard let ipv4 = IPv4Address(address_data) else { - throw - DecodingError - .dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unable to decode IP v4 address" - )) - - } + let address_string = try values.decode(String.self, forKey: .address) + if let ipv4 = IPv4Address(address_string) { address = ipv4 - case 16: - guard let ipv6 = IPv6Address(address_data) else { - throw - DecodingError - .dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unable to decode IP v6 address" - )) - - } + } else if let ipv6 = IPv6Address(address_string) { address = ipv6 - default: - throw DecodingError.typeMismatch( - IpAddrMask.self, - DecodingError.Context( - codingPath: decoder.codingPath, debugDescription: "Invalid IP address length" - )) + } else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unable to decode IP address" + )) } + cidr = try values.decode(UInt8.self, forKey: .cidr) } diff --git a/client/ios/VPNExtension/PacketTunnelProvider.swift b/client/ios/VPNExtension/PacketTunnelProvider.swift index 159bf97d..d6aa56ae 100644 --- a/client/ios/VPNExtension/PacketTunnelProvider.swift +++ b/client/ios/VPNExtension/PacketTunnelProvider.swift @@ -1,96 +1,97 @@ import NetworkExtension -import os -import Network -enum VPNEventType: String { - case tunnelUp = "tunnel_up" - case tunnelDown = "tunnel_down" - case tunnelError = "tunnel_error" - case connectionStatusChanged = "connection_status_changed" - case bytesTransferred = "bytes_transferred" +enum WireGuardTunnelError: Error { + case invalidTunnelConfiguration } class PacketTunnelProvider: NEPacketTunnelProvider { - /// Logging - private var logger = Logger( - subsystem: Bundle.main.bundleIdentifier!, - category: "PacketTunnelProvider" - ) + /// Unified logger (writes to both system log and file) + private let log = Log(category: "PacketTunnelProvider") private lazy var adapter: Adapter = { return Adapter(with: self) }() - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - guard let tunnelConfig = extractTunnelConfiguration() else { - let error = NSError(domain: "VPNExtension", code: -1, - userInfo: [NSLocalizedDescriptionKey: "Tunnel configuration is missing or invalid."]) - logger.error("Tunnel configuration is missing or invalid.") - completionHandler(error) - return + override func startTunnel( + options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void + ) { + if let options = options { + log.debug("Options: \(options)") } - logger.log("Starting tunnel with configuration: \(String(describing: tunnelConfig), privacy: .public)") + guard let protocolConfig = self.protocolConfiguration as? NETunnelProviderProtocol, + let providerConfig = protocolConfig.providerConfiguration + else { + log.error("Failed to parse provider configuration") + completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) + return + } - guard Endpoint(from: tunnelConfig.endpoint) != nil else { - let error = NSError(domain: "VPNExtension", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid endpoint format: \(tunnelConfig.endpoint)"]) - logger.error("Invalid endpoint format: \(tunnelConfig.endpoint, privacy: .public)") - completionHandler(error) +#if os(macOS) + guard let tunnelConfig = try? TunnelConfiguration.from(dictionary: providerConfig) + else { + log.error("Failed to parse tunnel configuration") + completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) return } +#else + guard let startData = try? TunnelStartData.from(dictionary: providerConfig) + else { + log.error("Failed to parse tunnel configuration") + completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) + return + } + let tunnelConfig = TunnelConfiguration(fromStartData: startData) +#endif - let tunnelConfiguration = TunnelConfiguration(fromStartData: tunnelConfig) - let networkSettings = tunnelConfiguration.asNetworkSettings() - - setTunnelNetworkSettings(networkSettings) { [weak self] error in - guard let self = self else { return } - - if let error = error { - logger.warning("Set tunnel network settings returned an error \(error, privacy: .public)") - completionHandler(error) - return - } - - do { - try self.adapter.start(tunnelConfiguration: tunnelConfiguration) - } catch { - logger.error("Failed to start adapter with error: \(error.localizedDescription, privacy: .public)") - completionHandler(error) - return + let networkSettings = tunnelConfig.asNetworkSettings() + self.setTunnelNetworkSettings(networkSettings) { error in + if error != nil { + self.log.error("Failed to set tunnel network settings: \(String(describing: error))") } + completionHandler(error) + return + } - logger.log("Tunnel started successfully") - completionHandler(nil) + do { + try adapter.start(tunnelConfiguration: tunnelConfig) + } catch { + log.error("Failed to start tunnel: \(error)") + completionHandler(error) } + log.info("Tunnel started successfully") + + completionHandler(nil) } - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - self.adapter.stop() + override func stopTunnel( + with reason: NEProviderStopReason, completionHandler: @escaping () -> Void + ) { + adapter.stop() + log.info("Tunnel stopped") completionHandler() } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - logger.debug("\(#function)") + // TODO: messageData should contain a valid message. if let handler = completionHandler { - handler(messageData) + if let stats = adapter.stats() { + let data = try? JSONEncoder().encode(stats) + handler(data) + } else { + handler(nil) + } } } override func sleep(completionHandler: @escaping () -> Void) { - logger.debug("\(#function)") + log.info("System going to sleep") + // Add code here to get ready to sleep. completionHandler() } override func wake() { - logger.debug("\(#function)") - } - - // MARK: - Helpers - - private func extractTunnelConfiguration() -> TunnelStartData? { - guard let providerConfig = (self.protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration as? [String: Any] else { - return nil - } - return try? TunnelStartData.from(dictionary: providerConfig) + log.info("System waking up") + // Add code here to wake up. } } diff --git a/client/ios/VPNExtension/Peer.swift b/client/ios/VPNExtension/Peer.swift index e30b61b9..fc07220a 100644 --- a/client/ios/VPNExtension/Peer.swift +++ b/client/ios/VPNExtension/Peer.swift @@ -4,23 +4,26 @@ final class Peer: Codable { var publicKey: String var preSharedKey: String? var endpoint: Endpoint? + var persistentKeepAlive: UInt16? + var allowedIPs = [IpAddrMask]() + // Statistics var lastHandshake: Date? var txBytes: UInt64 = 0 var rxBytes: UInt64 = 0 - var persistentKeepAlive: UInt16? - var allowedIPs = [IpAddrMask]() - init(publicKey: String, preSharedKey: String? = nil, endpoint: Endpoint? = nil, - lastHandshake: Date? = nil, txBytes: UInt64 = 0, rxBytes: UInt64 = 0, - persistentKeepAlive: UInt16? = nil, allowedIPs: [IpAddrMask] = [IpAddrMask]()) { + init( + publicKey: String, preSharedKey: String? = nil, endpoint: Endpoint? = nil, + persistentKeepAlive: UInt16? = nil, allowedIPs: [IpAddrMask] = [IpAddrMask](), + lastHandshake: Date? = nil, txBytes: UInt64 = 0, rxBytes: UInt64 = 0, + ) { self.publicKey = publicKey self.preSharedKey = preSharedKey self.endpoint = endpoint + self.persistentKeepAlive = persistentKeepAlive + self.allowedIPs = allowedIPs self.lastHandshake = lastHandshake self.txBytes = txBytes self.rxBytes = rxBytes - self.persistentKeepAlive = persistentKeepAlive - self.allowedIPs = allowedIPs } init(publicKey: String) { @@ -31,10 +34,11 @@ final class Peer: Codable { case publicKey case preSharedKey case endpoint - case lastHandshake - case txBytes - case rxBytes case persistentKeepAlive case allowedIPs + // There isn't any need to encode/decode these ephemeral fields. + // case lastHandshake + // case txBytes + // case rxBytes } } diff --git a/client/ios/VPNExtension/Stats.swift b/client/ios/VPNExtension/Stats.swift new file mode 100644 index 00000000..2c833446 --- /dev/null +++ b/client/ios/VPNExtension/Stats.swift @@ -0,0 +1,18 @@ +import ObjectiveC + +public class Stats: NSObject, Codable { + var txBytes: UInt64 + var rxBytes: UInt64 + var lastHandshake: UInt64 + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? + + init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64, locationId: UInt64?, tunnelId: UInt64?) { + self.txBytes = txBytes + self.rxBytes = rxBytes + self.lastHandshake = lastHandshake + self.locationId = locationId + self.tunnelId = tunnelId + } +} diff --git a/client/ios/VPNExtension/TunnelConfiguration.swift b/client/ios/VPNExtension/TunnelConfiguration.swift index 33765df2..ca4f76e7 100644 --- a/client/ios/VPNExtension/TunnelConfiguration.swift +++ b/client/ios/VPNExtension/TunnelConfiguration.swift @@ -2,14 +2,23 @@ import Foundation import NetworkExtension final class TunnelConfiguration: Codable { - var name: String - var interface: InterfaceConfiguration - var peers: [Peer] + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? - init(name: String, interface: InterfaceConfiguration, peers: [Peer]) { - self.interface = interface - self.peers = peers + var name: String + var privateKey: String + var addresses: [IpAddrMask] = [] + var listenPort: UInt16? + var peers: [Peer] = [] + var mtu: UInt32? + var dns: [String] = [] + var dnsSearch: [String] = [] + + init(name: String, privateKey: String, peers: [Peer]) { self.name = name + self.privateKey = privateKey + self.peers = peers let peerPublicKeysArray = peers.map { $0.publicKey } let peerPublicKeysSet = Set(peerPublicKeysArray) @@ -18,11 +27,18 @@ final class TunnelConfiguration: Codable { } } - // Only encode these properties. + /// Only encode these properties. enum CodingKeys: String, CodingKey { + case locationId + case tunnelId case name - case interface + case privateKey + case addresses + case listenPort case peers + case mtu + case dns + case dnsSearch } func asNetworkSettings() -> NEPacketTunnelNetworkSettings { @@ -32,31 +48,31 @@ final class TunnelConfiguration: Codable { let (ipv4IncludedRoutes, ipv6IncludedRoutes) = routes() // IPv4 addresses - let addrs_v4 = interface.addresses.filter { $0.address is IPv4Address } + let addrs_v4 = addresses.filter { $0.address is IPv4Address } .map { String(describing: $0.address) } - let masks_v4 = interface.addresses.filter { $0.address is IPv4Address } + let masks_v4 = addresses.filter { $0.address is IPv4Address } .map { String(describing: $0.mask()) } let ipv4Settings = NEIPv4Settings(addresses: addrs_v4, subnetMasks: masks_v4) ipv4Settings.includedRoutes = ipv4IncludedRoutes networkSettings.ipv4Settings = ipv4Settings // IPv6 addresses - let addrs_v6 = interface.addresses.filter { $0.address is IPv6Address } + let addrs_v6 = addresses.filter { $0.address is IPv6Address } .map { String(describing: $0.address) } // IMPORTANT: macOS/iOS has limitations handling IPv6 prefix masks longer than /120 due to // standards compliance and implementation choices in its network stack. - let masks_v6 = interface.addresses.filter { $0.address is IPv6Address } + let masks_v6 = addresses.filter { $0.address is IPv6Address } .map { NSNumber(value: min(120, $0.cidr)) } let ipv6Settings = NEIPv6Settings(addresses: addrs_v6, networkPrefixLengths: masks_v6) ipv6Settings.includedRoutes = ipv6IncludedRoutes networkSettings.ipv6Settings = ipv6Settings - networkSettings.mtu = interface.mtu as NSNumber? + networkSettings.mtu = mtu as NSNumber? networkSettings.tunnelOverheadBytes = 80 - let dnsSettings = NEDNSSettings(servers: interface.dns) - dnsSettings.searchDomains = interface.dnsSearch - if !interface.dns.isEmpty { + let dnsSettings = NEDNSSettings(servers: dns) + dnsSettings.searchDomains = dnsSearch + if !dns.isEmpty { // Make all DNS queries go through the tunnel. dnsSettings.matchDomains = [""] } @@ -71,7 +87,7 @@ final class TunnelConfiguration: Codable { var ipv6IncludedRoutes = [NEIPv6Route]() // Routes to interface addresses. - for addr_mask in interface.addresses { + for addr_mask in addresses { if addr_mask.address is IPv4Address { let route = NEIPv4Route( destinationAddress: "\(addr_mask.maskedAddress())", @@ -108,6 +124,7 @@ final class TunnelConfiguration: Codable { return (ipv4IncludedRoutes, ipv6IncludedRoutes) } +#if os(iOS) /// Helper function allowing to parse comma-separated string of addresses. private func parseAddresses(fromString string: String) -> [IpAddrMask] { var addresses: [IpAddrMask] = [] @@ -125,23 +142,22 @@ final class TunnelConfiguration: Codable { init(fromStartData startData: TunnelStartData) { name = startData.locationName - interface = InterfaceConfiguration(privateKey: startData.privateKey) + privateKey = startData.privateKey let peer = Peer(publicKey: startData.publicKey) peers = [peer] - interface.addresses = self.parseAddresses(fromString: startData.address) + addresses = self.parseAddresses(fromString: startData.address) // DNS settings - let dnsRecords = - startData.dns?.split(separator: ",").map { - $0.trimmingCharacters(in: .whitespaces) - } ?? [] + let dnsRecords = startData.dns?.split(separator: ",").map { + $0.trimmingCharacters(in: .whitespaces) + } ?? [] if !dnsRecords.isEmpty { for record in dnsRecords { if IPv4Address(record) != nil || IPv6Address(record) != nil { - interface.dns.append(record) + dns.append(record) } else { - interface.dnsSearch.append(record) + dnsSearch.append(record) } } } @@ -151,7 +167,7 @@ final class TunnelConfiguration: Codable { peer.endpoint = Endpoint(from: startData.endpoint) peer.persistentKeepAlive = UInt16(startData.keepalive) peer.allowedIPs = - switch startData.traffic { + switch startData.traffic { case .All: [ IpAddrMask(address: IPv4Address.any, cidr: 0), @@ -159,14 +175,12 @@ final class TunnelConfiguration: Codable { ] case .Predefined: self.parseAddresses(fromString: startData.allowedIps) - } + } } -} +#endif -//extension TunnelConfiguration: Equatable { -// public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool { -// return lhs.name == rhs.name && -// lhs.interface == rhs.interface && -// Set(lhs.peers) == Set(rhs.peers) -// } -//} + /// Client connection expects one peer, so check for that. + func isValidForClientConnection() -> Bool { + return peers.count == 1 + } +} diff --git a/client/ios/boringtun b/client/ios/boringtun index b990805f..b7c29222 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit b990805fc1637eeaa401bc156adf0dd28b2e50b8 +Subproject commit b7c29222f9881165e514088cc8f6c6463e0aa452 diff --git a/client/pubspec.lock b/client/pubspec.lock index dfff8949..11d313c9 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.12.4" + version: "8.12.6" characters: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: "direct main" description: name: cookie_jar - sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de + sha256: "963da02c1ef64cb5ac20de948c9e5940aa351f1e34a12b1d327c83d85b7e8fff" url: "https://pub.dev" source: hosted - version: "4.0.8" + version: "4.0.9" coverage: dependency: transitive description: @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" custom_lint: dependency: "direct dev" description: @@ -397,26 +397,26 @@ packages: dependency: "direct main" description: name: dio - sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c url: "https://pub.dev" source: hosted - version: "5.9.1" + version: "5.9.2" dio_cookie_manager: dependency: "direct main" description: name: dio_cookie_manager - sha256: d39c16abcc711c871b7b29bd51c6b5f3059ef39503916c6a9df7e22c4fc595e0 + sha256: "0db1a7b997a0455e488ac35744c68eed3f2a4280d3ab531835a65641b0a08744" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.4.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" drift: dependency: "direct main" description: @@ -562,10 +562,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" url: "https://pub.dev" source: hosted - version: "2.0.33" + version: "2.0.34" flutter_riverpod: dependency: "direct main" description: @@ -634,10 +634,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -708,18 +708,18 @@ packages: dependency: transitive description: name: gtk - sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" hooks: dependency: transitive description: name: hooks - sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.3" hooks_riverpod: dependency: "direct main" description: @@ -732,10 +732,10 @@ packages: dependency: transitive description: name: hotreloader - sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" html: dependency: transitive description: @@ -964,10 +964,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" url: "https://pub.dev" source: hosted - version: "0.17.4" + version: "0.17.6" node_preamble: dependency: transitive description: @@ -1036,10 +1036,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" url: "https://pub.dev" source: hosted - version: "2.2.22" + version: "2.2.23" path_provider_foundation: dependency: transitive description: @@ -1192,6 +1192,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -1260,18 +1268,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 url: "https://pub.dev" source: hosted - version: "2.4.20" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: @@ -1292,10 +1300,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" shared_preferences_web: dependency: transitive description: @@ -1569,10 +1577,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" url: "https://pub.dev" source: hosted - version: "6.3.28" + version: "6.3.29" url_launcher_ios: dependency: transitive description: @@ -1609,10 +1617,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1633,10 +1641,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.2.2" vector_graphics_codec: dependency: transitive description: @@ -1649,10 +1657,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" + sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.3" vector_math: dependency: transitive description: @@ -1665,10 +1673,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.2.0" watcher: dependency: transitive description: From 2d68495f86a6d0943e1abe42abfa133874500593 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 11:26:39 +0200 Subject: [PATCH 16/65] add Location::posture_check_required field --- .../defguard/drift_schema_v4.json | 1 + client/lib/data/db/database.dart | 10 +- client/lib/data/db/database.g.dart | 90 +- client/lib/data/db/database.steps.dart | 120 +- client/lib/data/proxy/enrollment.dart | 5 +- client/lib/data/proxy/enrollment.g.dart | 7 + .../test/drift/defguard/generated/schema.dart | 5 +- .../drift/defguard/generated/schema_v4.dart | 1372 +++++++++++++++++ 8 files changed, 1603 insertions(+), 7 deletions(-) create mode 100644 client/drift_schemas/defguard/drift_schema_v4.json create mode 100644 client/test/drift/defguard/generated/schema_v4.dart diff --git a/client/drift_schemas/defguard/drift_schema_v4.json b/client/drift_schemas/defguard/drift_schema_v4.json new file mode 100644 index 00000000..22312c31 --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v4.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"openid_display_name","getter_name":"openidDisplayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}},{"name":"posture_check_required","getter_name":"postureCheckRequired","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"posture_check_required\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"posture_check_required\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 65d5e55f..eb9c7aa2 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -94,6 +94,8 @@ class Locations extends Table with AutoIncrementingPrimaryKey { @JsonKey('location_mfa_mode') IntColumn get locationMfaMode => integer().nullable().map(const LocationMfaModeConverter())(); + @JsonKey('posture_check_required') + BoolColumn get postureCheckRequired => boolean().nullable()(); } @DriftDatabase(tables: [DefguardInstances, Locations]) @@ -101,7 +103,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 3; + int get schemaVersion => 4; @override MigrationStrategy get migration { @@ -132,6 +134,12 @@ class AppDatabase extends _$AppDatabase { schema.defguardInstances.openidDisplayName, ); }, + from3To4: (m, schema) async { + await m.addColumn( + schema.locations, + schema.locations.postureCheckRequired, + ); + }, ), ); } diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index da5c8d3a..1638e6f8 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -992,6 +992,19 @@ class $LocationsTable extends Locations type: DriftSqlType.int, requiredDuringInsert: false, ).withConverter($LocationsTable.$converterlocationMfaModen); + static const VerificationMeta _postureCheckRequiredMeta = + const VerificationMeta('postureCheckRequired'); + @override + late final GeneratedColumn postureCheckRequired = GeneratedColumn( + 'posture_check_required', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("posture_check_required" IN (0, 1))', + ), + ); @override List get $columns => [ id, @@ -1008,6 +1021,7 @@ class $LocationsTable extends Locations mfaMethod, keepAliveInterval, locationMfaMode, + postureCheckRequired, ]; @override String get aliasedName => _alias ?? actualTableName; @@ -1103,6 +1117,15 @@ class $LocationsTable extends Locations } else if (isInserting) { context.missing(_keepAliveIntervalMeta); } + if (data.containsKey('posture_check_required')) { + context.handle( + _postureCheckRequiredMeta, + postureCheckRequired.isAcceptableOrUnknown( + data['posture_check_required']!, + _postureCheckRequiredMeta, + ), + ); + } return context; } @@ -1174,6 +1197,10 @@ class $LocationsTable extends Locations data['${effectivePrefix}location_mfa_mode'], ), ), + postureCheckRequired: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}posture_check_required'], + ), ); } @@ -1215,6 +1242,7 @@ class Location extends DataClass implements Insertable { final MfaMethod? mfaMethod; final int keepAliveInterval; final LocationMfaMode? locationMfaMode; + final bool? postureCheckRequired; const Location({ required this.id, required this.instance, @@ -1230,6 +1258,7 @@ class Location extends DataClass implements Insertable { this.mfaMethod, required this.keepAliveInterval, this.locationMfaMode, + this.postureCheckRequired, }); @override Map toColumns(bool nullToAbsent) { @@ -1264,6 +1293,9 @@ class Location extends DataClass implements Insertable { $LocationsTable.$converterlocationMfaModen.toSql(locationMfaMode), ); } + if (!nullToAbsent || postureCheckRequired != null) { + map['posture_check_required'] = Variable(postureCheckRequired); + } return map; } @@ -1291,6 +1323,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: locationMfaMode == null && nullToAbsent ? const Value.absent() : Value(locationMfaMode), + postureCheckRequired: postureCheckRequired == null && nullToAbsent + ? const Value.absent() + : Value(postureCheckRequired), ); } @@ -1318,6 +1353,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: serializer.fromJson( json['location_mfa_mode'], ), + postureCheckRequired: serializer.fromJson( + json['posture_check_required'], + ), ); } @override @@ -1340,6 +1378,7 @@ class Location extends DataClass implements Insertable { 'mfa_method': serializer.toJson(mfaMethod), 'keepalive_interval': serializer.toJson(keepAliveInterval), 'location_mfa_mode': serializer.toJson(locationMfaMode), + 'posture_check_required': serializer.toJson(postureCheckRequired), }; } @@ -1358,6 +1397,7 @@ class Location extends DataClass implements Insertable { Value mfaMethod = const Value.absent(), int? keepAliveInterval, Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), }) => Location( id: id ?? this.id, instance: instance ?? this.instance, @@ -1377,6 +1417,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: locationMfaMode.present ? locationMfaMode.value : this.locationMfaMode, + postureCheckRequired: postureCheckRequired.present + ? postureCheckRequired.value + : this.postureCheckRequired, ); Location copyWithCompanion(LocationsCompanion data) { return Location( @@ -1404,6 +1447,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: data.locationMfaMode.present ? data.locationMfaMode.value : this.locationMfaMode, + postureCheckRequired: data.postureCheckRequired.present + ? data.postureCheckRequired.value + : this.postureCheckRequired, ); } @@ -1423,7 +1469,8 @@ class Location extends DataClass implements Insertable { ..write('trafficMethod: $trafficMethod, ') ..write('mfaMethod: $mfaMethod, ') ..write('keepAliveInterval: $keepAliveInterval, ') - ..write('locationMfaMode: $locationMfaMode') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') ..write(')')) .toString(); } @@ -1444,6 +1491,7 @@ class Location extends DataClass implements Insertable { mfaMethod, keepAliveInterval, locationMfaMode, + postureCheckRequired, ); @override bool operator ==(Object other) => @@ -1462,7 +1510,8 @@ class Location extends DataClass implements Insertable { other.trafficMethod == this.trafficMethod && other.mfaMethod == this.mfaMethod && other.keepAliveInterval == this.keepAliveInterval && - other.locationMfaMode == this.locationMfaMode); + other.locationMfaMode == this.locationMfaMode && + other.postureCheckRequired == this.postureCheckRequired); } class LocationsCompanion extends UpdateCompanion { @@ -1480,6 +1529,7 @@ class LocationsCompanion extends UpdateCompanion { final Value mfaMethod; final Value keepAliveInterval; final Value locationMfaMode; + final Value postureCheckRequired; const LocationsCompanion({ this.id = const Value.absent(), this.instance = const Value.absent(), @@ -1495,6 +1545,7 @@ class LocationsCompanion extends UpdateCompanion { this.mfaMethod = const Value.absent(), this.keepAliveInterval = const Value.absent(), this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), }); LocationsCompanion.insert({ this.id = const Value.absent(), @@ -1511,6 +1562,7 @@ class LocationsCompanion extends UpdateCompanion { this.mfaMethod = const Value.absent(), required int keepAliveInterval, this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), }) : instance = Value(instance), networkId = Value(networkId), name = Value(name), @@ -1534,6 +1586,7 @@ class LocationsCompanion extends UpdateCompanion { Expression? mfaMethod, Expression? keepAliveInterval, Expression? locationMfaMode, + Expression? postureCheckRequired, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1550,6 +1603,8 @@ class LocationsCompanion extends UpdateCompanion { if (mfaMethod != null) 'mfa_method': mfaMethod, if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + if (postureCheckRequired != null) + 'posture_check_required': postureCheckRequired, }); } @@ -1568,6 +1623,7 @@ class LocationsCompanion extends UpdateCompanion { Value? mfaMethod, Value? keepAliveInterval, Value? locationMfaMode, + Value? postureCheckRequired, }) { return LocationsCompanion( id: id ?? this.id, @@ -1584,6 +1640,7 @@ class LocationsCompanion extends UpdateCompanion { mfaMethod: mfaMethod ?? this.mfaMethod, keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, locationMfaMode: locationMfaMode ?? this.locationMfaMode, + postureCheckRequired: postureCheckRequired ?? this.postureCheckRequired, ); } @@ -1638,6 +1695,11 @@ class LocationsCompanion extends UpdateCompanion { $LocationsTable.$converterlocationMfaModen.toSql(locationMfaMode.value), ); } + if (postureCheckRequired.present) { + map['posture_check_required'] = Variable( + postureCheckRequired.value, + ); + } return map; } @@ -1657,7 +1719,8 @@ class LocationsCompanion extends UpdateCompanion { ..write('trafficMethod: $trafficMethod, ') ..write('mfaMethod: $mfaMethod, ') ..write('keepAliveInterval: $keepAliveInterval, ') - ..write('locationMfaMode: $locationMfaMode') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') ..write(')')) .toString(); } @@ -2204,6 +2267,7 @@ typedef $$LocationsTableCreateCompanionBuilder = Value mfaMethod, required int keepAliveInterval, Value locationMfaMode, + Value postureCheckRequired, }); typedef $$LocationsTableUpdateCompanionBuilder = LocationsCompanion Function({ @@ -2221,6 +2285,7 @@ typedef $$LocationsTableUpdateCompanionBuilder = Value mfaMethod, Value keepAliveInterval, Value locationMfaMode, + Value postureCheckRequired, }); final class $$LocationsTableReferences @@ -2324,6 +2389,11 @@ class $$LocationsTableFilterComposer builder: (column) => ColumnWithTypeConverterFilters(column), ); + ColumnFilters get postureCheckRequired => $composableBuilder( + column: $table.postureCheckRequired, + builder: (column) => ColumnFilters(column), + ); + $$DefguardInstancesTableFilterComposer get instance { final $$DefguardInstancesTableFilterComposer composer = $composerBuilder( composer: this, @@ -2422,6 +2492,11 @@ class $$LocationsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get postureCheckRequired => $composableBuilder( + column: $table.postureCheckRequired, + builder: (column) => ColumnOrderings(column), + ); + $$DefguardInstancesTableOrderingComposer get instance { final $$DefguardInstancesTableOrderingComposer composer = $composerBuilder( composer: this, @@ -2506,6 +2581,11 @@ class $$LocationsTableAnnotationComposer builder: (column) => column, ); + GeneratedColumn get postureCheckRequired => $composableBuilder( + column: $table.postureCheckRequired, + builder: (column) => column, + ); + $$DefguardInstancesTableAnnotationComposer get instance { final $$DefguardInstancesTableAnnotationComposer composer = $composerBuilder( @@ -2573,6 +2653,7 @@ class $$LocationsTableTableManager Value mfaMethod = const Value.absent(), Value keepAliveInterval = const Value.absent(), Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), }) => LocationsCompanion( id: id, instance: instance, @@ -2588,6 +2669,7 @@ class $$LocationsTableTableManager mfaMethod: mfaMethod, keepAliveInterval: keepAliveInterval, locationMfaMode: locationMfaMode, + postureCheckRequired: postureCheckRequired, ), createCompanionCallback: ({ @@ -2605,6 +2687,7 @@ class $$LocationsTableTableManager Value mfaMethod = const Value.absent(), required int keepAliveInterval, Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), }) => LocationsCompanion.insert( id: id, instance: instance, @@ -2620,6 +2703,7 @@ class $$LocationsTableTableManager mfaMethod: mfaMethod, keepAliveInterval: keepAliveInterval, locationMfaMode: locationMfaMode, + postureCheckRequired: postureCheckRequired, ), withReferenceMapper: (p0) => p0 .map( diff --git a/client/lib/data/db/database.steps.dart b/client/lib/data/db/database.steps.dart index 237aeba7..8f97ee00 100644 --- a/client/lib/data/db/database.steps.dart +++ b/client/lib/data/db/database.steps.dart @@ -413,9 +413,117 @@ i1.GeneratedColumn _column_24(String aliasedName) => true, type: i1.DriftSqlType.string, ); + +final class Schema4 extends i0.VersionedSchema { + Schema4({required super.database}) : super(version: 4); + @override + late final List entities = [ + defguardInstances, + locations, + ]; + late final Shape2 defguardInstances = Shape2( + source: i0.VersionedTable( + entityName: 'defguard_instances', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_24, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 locations = Shape3( + source: i0.VersionedTable( + entityName: 'locations', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_13, + _column_14, + _column_1, + _column_15, + _column_10, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + _column_25, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape3 extends i0.VersionedTable { + Shape3({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => + columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get networkId => + columnsByName['network_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get address => + columnsByName['address']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get endpoint => + columnsByName['endpoint']! as i1.GeneratedColumn; + i1.GeneratedColumn get allowedIps => + columnsByName['allowed_ips']! as i1.GeneratedColumn; + i1.GeneratedColumn get dns => + columnsByName['dns']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaEnabled => + columnsByName['mfa_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get trafficMethod => + columnsByName['traffic_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaMethod => + columnsByName['mfa_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get keepAliveInterval => + columnsByName['keep_alive_interval']! as i1.GeneratedColumn; + i1.GeneratedColumn get locationMfaMode => + columnsByName['location_mfa_mode']! as i1.GeneratedColumn; + i1.GeneratedColumn get postureCheckRequired => + columnsByName['posture_check_required']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_25(String aliasedName) => + i1.GeneratedColumn( + 'posture_check_required', + aliasedName, + true, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("posture_check_required" IN (0, 1))', + ), + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -429,6 +537,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from2To3(migrator, schema); return 3; + case 3: + final schema = Schema4(database: database); + final migrator = i1.Migrator(database, schema); + await from3To4(migrator, schema); + return 4; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -438,6 +551,11 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) => i0.VersionedSchema.stepByStepHelper( - step: migrationSteps(from1To2: from1To2, from2To3: from2To3), + step: migrationSteps( + from1To2: from1To2, + from2To3: from2To3, + from3To4: from3To4, + ), ); diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index 87e5d080..adf0c123 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -152,6 +152,7 @@ class DeviceConfig { final bool mfaEnabled; final int keepaliveInterval; final LocationMfaMode? locationMfaMode; + final bool? postureCheckRequired; factory DeviceConfig.fromJson(Map json) => _$DeviceConfigFromJson(json); @@ -170,6 +171,7 @@ class DeviceConfig { required this.mfaEnabled, required this.keepaliveInterval, this.locationMfaMode, + this.postureCheckRequired, }); bool matchesLocation(Location other) { @@ -182,7 +184,8 @@ class DeviceConfig { dns == other.dns && mfaEnabled == other.mfaEnabled && keepaliveInterval == other.keepAliveInterval && - locationMfaMode == other.locationMfaMode; + locationMfaMode == other.locationMfaMode && + postureCheckRequired == other.postureCheckRequired; } LocationsCompanion toCompanion({ diff --git a/client/lib/data/proxy/enrollment.g.dart b/client/lib/data/proxy/enrollment.g.dart index 5f5cf4b3..1eb38072 100644 --- a/client/lib/data/proxy/enrollment.g.dart +++ b/client/lib/data/proxy/enrollment.g.dart @@ -263,6 +263,10 @@ DeviceConfig _$DeviceConfigFromJson(Map json) => 'location_mfa_mode', (v) => $enumDecodeNullable(_$LocationMfaModeEnumMap, v), ), + postureCheckRequired: $checkedConvert( + 'posture_check_required', + (v) => v as bool?, + ), ); return val; }, @@ -274,6 +278,7 @@ DeviceConfig _$DeviceConfigFromJson(Map json) => 'mfaEnabled': 'mfa_enabled', 'keepaliveInterval': 'keepalive_interval', 'locationMfaMode': 'location_mfa_mode', + 'postureCheckRequired': 'posture_check_required', }, ); @@ -289,6 +294,7 @@ const _$DeviceConfigFieldMap = { 'mfaEnabled': 'mfa_enabled', 'keepaliveInterval': 'keepalive_interval', 'locationMfaMode': 'location_mfa_mode', + 'postureCheckRequired': 'posture_check_required', }; Map _$DeviceConfigToJson(DeviceConfig instance) => @@ -304,6 +310,7 @@ Map _$DeviceConfigToJson(DeviceConfig instance) => 'mfa_enabled': instance.mfaEnabled, 'keepalive_interval': instance.keepaliveInterval, 'location_mfa_mode': _$LocationMfaModeEnumMap[instance.locationMfaMode], + 'posture_check_required': instance.postureCheckRequired, }; const _$LocationMfaModeEnumMap = { diff --git a/client/test/drift/defguard/generated/schema.dart b/client/test/drift/defguard/generated/schema.dart index 209e70d7..22131b11 100644 --- a/client/test/drift/defguard/generated/schema.dart +++ b/client/test/drift/defguard/generated/schema.dart @@ -6,6 +6,7 @@ import 'package:drift/internal/migrations.dart'; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; import 'schema_v3.dart' as v3; +import 'schema_v4.dart' as v4; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -17,10 +18,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v2.DatabaseAtV2(db); case 3: return v3.DatabaseAtV3(db); + case 4: + return v4.DatabaseAtV4(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3]; + static const versions = const [1, 2, 3, 4]; } diff --git a/client/test/drift/defguard/generated/schema_v4.dart b/client/test/drift/defguard/generated/schema_v4.dart new file mode 100644 index 00000000..c6c2fcc2 --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v4.dart @@ -0,0 +1,1372 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn clientTrafficPolicy = GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + late final GeneratedColumn openidDisplayName = + GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + clientTrafficPolicy: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + openidDisplayName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}openid_display_name'], + ), + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final int clientTrafficPolicy; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + final String? openidDisplayName; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.clientTrafficPolicy, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + this.openidDisplayName, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['client_traffic_policy'] = Variable(clientTrafficPolicy); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + if (!nullToAbsent || openidDisplayName != null) { + map['openid_display_name'] = Variable(openidDisplayName); + } + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + clientTrafficPolicy: Value(clientTrafficPolicy), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + openidDisplayName: openidDisplayName == null && nullToAbsent + ? const Value.absent() + : Value(openidDisplayName), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + clientTrafficPolicy: serializer.fromJson( + json['clientTrafficPolicy'], + ), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + openidDisplayName: serializer.fromJson( + json['openidDisplayName'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'clientTrafficPolicy': serializer.toJson(clientTrafficPolicy), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + 'openidDisplayName': serializer.toJson(openidDisplayName), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + int? clientTrafficPolicy, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + Value openidDisplayName = const Value.absent(), + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName.present + ? openidDisplayName.value + : this.openidDisplayName, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + openidDisplayName: data.openidDisplayName.present + ? data.openidDisplayName.value + : this.openidDisplayName, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.clientTrafficPolicy == this.clientTrafficPolicy && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored && + other.openidDisplayName == this.openidDisplayName); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value clientTrafficPolicy; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + final Value openidDisplayName; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + this.openidDisplayName = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + this.clientTrafficPolicy = const Value.absent(), + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + this.openidDisplayName = const Value.absent(), + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? clientTrafficPolicy, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + Expression? openidDisplayName, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + if (openidDisplayName != null) 'openid_display_name': openidDisplayName, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? clientTrafficPolicy, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + Value? openidDisplayName, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName ?? this.openidDisplayName, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable(clientTrafficPolicy.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + if (openidDisplayName.present) { + map['openid_display_name'] = Variable(openidDisplayName.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn postureCheckRequired = GeneratedColumn( + 'posture_check_required', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("posture_check_required" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + postureCheckRequired, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + postureCheckRequired: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}posture_check_required'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + final bool? postureCheckRequired; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + this.postureCheckRequired, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + if (!nullToAbsent || postureCheckRequired != null) { + map['posture_check_required'] = Variable(postureCheckRequired); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + postureCheckRequired: postureCheckRequired == null && nullToAbsent + ? const Value.absent() + : Value(postureCheckRequired), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + postureCheckRequired: serializer.fromJson( + json['postureCheckRequired'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + 'postureCheckRequired': serializer.toJson(postureCheckRequired), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + postureCheckRequired: postureCheckRequired.present + ? postureCheckRequired.value + : this.postureCheckRequired, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + postureCheckRequired: data.postureCheckRequired.present + ? data.postureCheckRequired.value + : this.postureCheckRequired, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + postureCheckRequired, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode && + other.postureCheckRequired == this.postureCheckRequired); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + final Value postureCheckRequired; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + Expression? postureCheckRequired, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + if (postureCheckRequired != null) + 'posture_check_required': postureCheckRequired, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + Value? postureCheckRequired, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + postureCheckRequired: postureCheckRequired ?? this.postureCheckRequired, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + if (postureCheckRequired.present) { + map['posture_check_required'] = Variable( + postureCheckRequired.value, + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV4 extends GeneratedDatabase { + DatabaseAtV4(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 4; +} From c1ed5b826759271cb85d04f3363d85a9e40ec0c6 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 11:46:30 +0200 Subject: [PATCH 17/65] update deps, fix ndk version mismatch --- client/android/app/build.gradle.kts | 2 +- client/pubspec.lock | 32 +++++++++++------------------ flake.nix | 2 +- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 3082919f..4c434a44 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -8,7 +8,7 @@ plugins { android { namespace = "net.defguard.mobile" compileSdk = 36 - ndkVersion = "27.0.12077973" + ndkVersion = flutter.ndkVersion compileOptions { isCoreLibraryDesugaringEnabled = true diff --git a/client/pubspec.lock b/client/pubspec.lock index 11d313c9..4f57d029 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" code_builder: dependency: transitive description: @@ -716,10 +716,10 @@ packages: dependency: transitive description: name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.0" hooks_riverpod: dependency: "direct main" description: @@ -960,14 +960,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" - source: hosted - version: "0.17.6" node_preamble: dependency: transitive description: @@ -980,10 +972,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.4.1" package_config: dependency: transitive description: @@ -1044,10 +1036,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -1577,10 +1569,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" url: "https://pub.dev" source: hosted - version: "6.3.29" + version: "6.3.30" url_launcher_ios: dependency: transitive description: @@ -1773,5 +1765,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.3 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/flake.nix b/flake.nix index 002f76ab..c2ca9d36 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ allowUnfree = true; }; }; - ndkVersion = "27.0.12077973"; + ndkVersion = "28.2.13676358"; androidComposition = pkgs.androidenv.composeAndroidPackages { # buildToolsVersions = [ buildToolsVersion "28.0.3" ]; # platformVersions = [ "34" "28" ]; From 423c18406cc566c8d8f380aeeb08ddd8f5647597 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 12:42:56 +0200 Subject: [PATCH 18/65] allow plain http communication --- client/android/app/src/main/res/xml/network_security_config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/android/app/src/main/res/xml/network_security_config.xml b/client/android/app/src/main/res/xml/network_security_config.xml index d20fb833..8a76775f 100644 --- a/client/android/app/src/main/res/xml/network_security_config.xml +++ b/client/android/app/src/main/res/xml/network_security_config.xml @@ -1,6 +1,6 @@ - + From 822eb15df12e8e92a8f70ee7567316bb30214426 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 09:24:53 +0200 Subject: [PATCH 19/65] connect using dummy posture data --- client/lib/data/plugin/plugin.dart | 8 +- client/lib/data/plugin/plugin.g.dart | 7 + client/lib/data/proxy/enrollment.dart | 1 + client/lib/data/proxy/mfa.dart | 122 +++++++++++++ client/lib/data/proxy/mfa.g.dart | 168 ++++++++++++++++-- .../instance/services/tunnel_service.dart | 5 + 6 files changed, 297 insertions(+), 14 deletions(-) diff --git a/client/lib/data/plugin/plugin.dart b/client/lib/data/plugin/plugin.dart index 5bbc54c9..4b7b7a90 100644 --- a/client/lib/data/plugin/plugin.dart +++ b/client/lib/data/plugin/plugin.dart @@ -4,8 +4,6 @@ import '../db/enums.dart'; part 'plugin.g.dart'; - - @JsonSerializable() class PluginConnectPayload { // config @@ -25,6 +23,7 @@ class PluginConnectPayload { final int instanceId; final int networkId; RoutingMethod traffic; + final bool postureCheckRequired; PluginConnectPayload({ required this.publicKey, @@ -41,6 +40,7 @@ class PluginConnectPayload { required this.networkId, this.dns, this.presharedKey, + required this.postureCheckRequired, }); factory PluginConnectPayload.fromJson(Map json) => @@ -49,7 +49,6 @@ class PluginConnectPayload { Map toJson() => _$PluginConnectPayloadToJson(this); } - @JsonSerializable() class PluginTunnelEventData { final int instanceId; @@ -62,7 +61,8 @@ class PluginTunnelEventData { required this.traffic, }); - factory PluginTunnelEventData.fromJson(Map json) => _$PluginTunnelEventDataFromJson(json); + factory PluginTunnelEventData.fromJson(Map json) => + _$PluginTunnelEventDataFromJson(json); Map toJson() => _$PluginTunnelEventDataToJson(this); } diff --git a/client/lib/data/plugin/plugin.g.dart b/client/lib/data/plugin/plugin.g.dart index 1b85e6a9..6fb711c7 100644 --- a/client/lib/data/plugin/plugin.g.dart +++ b/client/lib/data/plugin/plugin.g.dart @@ -30,6 +30,10 @@ PluginConnectPayload _$PluginConnectPayloadFromJson( networkId: $checkedConvert('network_id', (v) => (v as num).toInt()), dns: $checkedConvert('dns', (v) => v as String?), presharedKey: $checkedConvert('preshared_key', (v) => v as String?), + postureCheckRequired: $checkedConvert( + 'posture_check_required', + (v) => v as bool, + ), ); return val; }, @@ -43,6 +47,7 @@ PluginConnectPayload _$PluginConnectPayloadFromJson( 'instanceId': 'instance_id', 'networkId': 'network_id', 'presharedKey': 'preshared_key', + 'postureCheckRequired': 'posture_check_required', }, ); @@ -61,6 +66,7 @@ const _$PluginConnectPayloadFieldMap = { 'instanceId': 'instance_id', 'networkId': 'network_id', 'traffic': 'traffic', + 'postureCheckRequired': 'posture_check_required', }; Map _$PluginConnectPayloadToJson( @@ -80,6 +86,7 @@ Map _$PluginConnectPayloadToJson( 'instance_id': instance.instanceId, 'network_id': instance.networkId, 'traffic': _$RoutingMethodEnumMap[instance.traffic]!, + 'posture_check_required': instance.postureCheckRequired, }; const _$RoutingMethodEnumMap = { diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index adf0c123..ffa0990b 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -209,6 +209,7 @@ class DeviceConfig { allowedIps: d.Value(allowedIps), address: d.Value(assignedIp), locationMfaMode: d.Value(locationMfaMode), + postureCheckRequired: d.Value(postureCheckRequired), ); } } diff --git a/client/lib/data/proxy/mfa.dart b/client/lib/data/proxy/mfa.dart index 55949ab8..f9c2e6cc 100644 --- a/client/lib/data/proxy/mfa.dart +++ b/client/lib/data/proxy/mfa.dart @@ -3,16 +3,138 @@ import 'package:mobile/data/db/enums.dart'; part 'mfa.g.dart'; +enum UnavailableReason { + unspecified(0), + insufficientPermissions(1), + notApplicable(2), + detectionFailed(3); + + final int value; + + const UnavailableReason(this.value); +} + +@JsonSerializable() +class StringCheck { + final Map result; + + const StringCheck({required this.result}); + + factory StringCheck.value(String value) => StringCheck( + result: {'Value': value}, + ); + + factory StringCheck.unavailable(UnavailableReason reason) => StringCheck( + result: {'Unavailable': reason.value}, + ); + + factory StringCheck.fromJson(Map json) => + _$StringCheckFromJson(json); + + Map toJson() => _$StringCheckToJson(this); +} + +@JsonSerializable() +class BoolCheck { + final Map result; + + const BoolCheck({required this.result}); + + factory BoolCheck.value(bool value) => BoolCheck( + result: {'Value': value}, + ); + + factory BoolCheck.unavailable(UnavailableReason reason) => BoolCheck( + result: {'Unavailable': reason.value}, + ); + + factory BoolCheck.fromJson(Map json) => + _$BoolCheckFromJson(json); + + Map toJson() => _$BoolCheckToJson(this); +} + +@JsonSerializable() +class Int32Check { + final Map result; + + const Int32Check({required this.result}); + + factory Int32Check.value(int value) => Int32Check( + result: {'Value': value}, + ); + + factory Int32Check.unavailable(UnavailableReason reason) => Int32Check( + result: {'Unavailable': reason.value}, + ); + + factory Int32Check.fromJson(Map json) => + _$Int32CheckFromJson(json); + + Map toJson() => _$Int32CheckToJson(this); +} + +@JsonSerializable() +class DevicePostureData { + final String defguardClientVersion; + final String osType; + final StringCheck? osName; + final StringCheck? osVersion; + final BoolCheck? diskEncryption; + final BoolCheck? antivirusPresent; + final BoolCheck? windowsAdDomainJoined; + final Int32Check? windowsSecurityUpdateAgeDays; + final StringCheck? linuxKernelVersion; + final BoolCheck? deviceIntegrity; + + const DevicePostureData({ + required this.defguardClientVersion, + required this.osType, + this.osName, + this.osVersion, + this.diskEncryption, + this.antivirusPresent, + this.windowsAdDomainJoined, + this.windowsSecurityUpdateAgeDays, + this.linuxKernelVersion, + this.deviceIntegrity, + }); + + factory DevicePostureData.fromJson(Map json) => + _$DevicePostureDataFromJson(json); + + Map toJson() => _$DevicePostureDataToJson(this); +} + +DevicePostureData getPosture() { + final notApplicable = UnavailableReason.notApplicable; + + return DevicePostureData( + defguardClientVersion: '2.1.0', + osType: 'Android', + osName: StringCheck.value('Android'), + osVersion: StringCheck.value('16'), + diskEncryption: BoolCheck.unavailable(notApplicable), + antivirusPresent: BoolCheck.unavailable(notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), + windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), + linuxKernelVersion: StringCheck.unavailable(notApplicable), + deviceIntegrity: BoolCheck.value(true), + ); +} + @JsonSerializable() class StartMfaRequest { final String pubkey; final int locationId; final MfaMethod method; + final DevicePostureData? postureData; const StartMfaRequest({ required this.pubkey, required this.locationId, required this.method, + this.postureData, }); factory StartMfaRequest.fromJson(Map json) => diff --git a/client/lib/data/proxy/mfa.g.dart b/client/lib/data/proxy/mfa.g.dart index b439a838..526ed523 100644 --- a/client/lib/data/proxy/mfa.g.dart +++ b/client/lib/data/proxy/mfa.g.dart @@ -6,23 +6,170 @@ part of 'mfa.dart'; // JsonSerializableGenerator // ************************************************************************** -StartMfaRequest _$StartMfaRequestFromJson(Map json) => - $checkedCreate('StartMfaRequest', json, ($checkedConvert) { - final val = StartMfaRequest( - pubkey: $checkedConvert('pubkey', (v) => v as String), - locationId: $checkedConvert('location_id', (v) => (v as num).toInt()), - method: $checkedConvert( - 'method', - (v) => $enumDecode(_$MfaMethodEnumMap, v), - ), +StringCheck _$StringCheckFromJson(Map json) => + $checkedCreate('StringCheck', json, ($checkedConvert) { + final val = StringCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$StringCheckFieldMap = {'result': 'result'}; + +Map _$StringCheckToJson(StringCheck instance) => + {'result': instance.result}; + +BoolCheck _$BoolCheckFromJson(Map json) => + $checkedCreate('BoolCheck', json, ($checkedConvert) { + final val = BoolCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$BoolCheckFieldMap = {'result': 'result'}; + +Map _$BoolCheckToJson(BoolCheck instance) => { + 'result': instance.result, +}; + +Int32Check _$Int32CheckFromJson(Map json) => + $checkedCreate('Int32Check', json, ($checkedConvert) { + final val = Int32Check( + result: $checkedConvert('result', (v) => v as Map), ); return val; - }, fieldKeyMap: const {'locationId': 'location_id'}); + }); + +const _$Int32CheckFieldMap = {'result': 'result'}; + +Map _$Int32CheckToJson(Int32Check instance) => + {'result': instance.result}; + +DevicePostureData _$DevicePostureDataFromJson( + Map json, +) => $checkedCreate( + 'DevicePostureData', + json, + ($checkedConvert) { + final val = DevicePostureData( + defguardClientVersion: $checkedConvert( + 'defguard_client_version', + (v) => v as String, + ), + osType: $checkedConvert('os_type', (v) => v as String), + osName: $checkedConvert( + 'os_name', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + osVersion: $checkedConvert( + 'os_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + diskEncryption: $checkedConvert( + 'disk_encryption', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + antivirusPresent: $checkedConvert( + 'antivirus_present', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsAdDomainJoined: $checkedConvert( + 'windows_ad_domain_joined', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsSecurityUpdateAgeDays: $checkedConvert( + 'windows_security_update_age_days', + (v) => + v == null ? null : Int32Check.fromJson(v as Map), + ), + linuxKernelVersion: $checkedConvert( + 'linux_kernel_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + deviceIntegrity: $checkedConvert( + 'device_integrity', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', + }, +); + +const _$DevicePostureDataFieldMap = { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', +}; + +Map _$DevicePostureDataToJson(DevicePostureData instance) => + { + 'defguard_client_version': instance.defguardClientVersion, + 'os_type': instance.osType, + 'os_name': instance.osName, + 'os_version': instance.osVersion, + 'disk_encryption': instance.diskEncryption, + 'antivirus_present': instance.antivirusPresent, + 'windows_ad_domain_joined': instance.windowsAdDomainJoined, + 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, + 'linux_kernel_version': instance.linuxKernelVersion, + 'device_integrity': instance.deviceIntegrity, + }; + +StartMfaRequest _$StartMfaRequestFromJson(Map json) => + $checkedCreate( + 'StartMfaRequest', + json, + ($checkedConvert) { + final val = StartMfaRequest( + pubkey: $checkedConvert('pubkey', (v) => v as String), + locationId: $checkedConvert('location_id', (v) => (v as num).toInt()), + method: $checkedConvert( + 'method', + (v) => $enumDecode(_$MfaMethodEnumMap, v), + ), + postureData: $checkedConvert( + 'posture_data', + (v) => v == null + ? null + : DevicePostureData.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'locationId': 'location_id', + 'postureData': 'posture_data', + }, + ); const _$StartMfaRequestFieldMap = { 'pubkey': 'pubkey', 'locationId': 'location_id', 'method': 'method', + 'postureData': 'posture_data', }; Map _$StartMfaRequestToJson(StartMfaRequest instance) => @@ -30,6 +177,7 @@ Map _$StartMfaRequestToJson(StartMfaRequest instance) => 'pubkey': instance.pubkey, 'location_id': instance.locationId, 'method': _$MfaMethodEnumMap[instance.method]!, + 'posture_data': instance.postureData, }; const _$MfaMethodEnumMap = { diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index b44cc4a8..97e1137e 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -152,6 +152,7 @@ class TunnelService { payload.devicePublicKey, payload.networkId, method, + payload.postureCheckRequired, ); if (method == MfaMethod.openid) { // perform openid-based MFA @@ -285,14 +286,17 @@ class TunnelService { String pubkey, int networkId, MfaMethod method, + bool postureCheckRequired, ) async { talker.debug( "Starting MFA for networkId: $networkId, method: ${method.toReadableString()}", ); + final postureData = postureCheckRequired ? getPosture() : null; final request = StartMfaRequest( pubkey: pubkey, locationId: networkId, method: method, + postureData: postureData, ); final uri = Uri.parse(url); @@ -319,6 +323,7 @@ class TunnelService { networkId: location.networkId, instanceId: instance.id, traffic: trafficMethod, + postureCheckRequired: location.postureCheckRequired == true, ); } From 6ab54b38318474398a86d51f3a084fed8c95be17 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 09:35:50 +0200 Subject: [PATCH 20/65] move posture structs to enterprise module --- client/lib/data/proxy/mfa.dart | 121 +-------------- client/lib/data/proxy/mfa.g.dart | 132 ----------------- client/lib/enterprise/postures.dart | 115 +++++++++++++++ client/lib/enterprise/postures.g.dart | 139 ++++++++++++++++++ .../instance/services/tunnel_service.dart | 1 + 5 files changed, 256 insertions(+), 252 deletions(-) create mode 100644 client/lib/enterprise/postures.dart create mode 100644 client/lib/enterprise/postures.g.dart diff --git a/client/lib/data/proxy/mfa.dart b/client/lib/data/proxy/mfa.dart index f9c2e6cc..a7601914 100644 --- a/client/lib/data/proxy/mfa.dart +++ b/client/lib/data/proxy/mfa.dart @@ -1,128 +1,9 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:mobile/data/db/enums.dart'; +import 'package:mobile/enterprise/postures.dart'; part 'mfa.g.dart'; -enum UnavailableReason { - unspecified(0), - insufficientPermissions(1), - notApplicable(2), - detectionFailed(3); - - final int value; - - const UnavailableReason(this.value); -} - -@JsonSerializable() -class StringCheck { - final Map result; - - const StringCheck({required this.result}); - - factory StringCheck.value(String value) => StringCheck( - result: {'Value': value}, - ); - - factory StringCheck.unavailable(UnavailableReason reason) => StringCheck( - result: {'Unavailable': reason.value}, - ); - - factory StringCheck.fromJson(Map json) => - _$StringCheckFromJson(json); - - Map toJson() => _$StringCheckToJson(this); -} - -@JsonSerializable() -class BoolCheck { - final Map result; - - const BoolCheck({required this.result}); - - factory BoolCheck.value(bool value) => BoolCheck( - result: {'Value': value}, - ); - - factory BoolCheck.unavailable(UnavailableReason reason) => BoolCheck( - result: {'Unavailable': reason.value}, - ); - - factory BoolCheck.fromJson(Map json) => - _$BoolCheckFromJson(json); - - Map toJson() => _$BoolCheckToJson(this); -} - -@JsonSerializable() -class Int32Check { - final Map result; - - const Int32Check({required this.result}); - - factory Int32Check.value(int value) => Int32Check( - result: {'Value': value}, - ); - - factory Int32Check.unavailable(UnavailableReason reason) => Int32Check( - result: {'Unavailable': reason.value}, - ); - - factory Int32Check.fromJson(Map json) => - _$Int32CheckFromJson(json); - - Map toJson() => _$Int32CheckToJson(this); -} - -@JsonSerializable() -class DevicePostureData { - final String defguardClientVersion; - final String osType; - final StringCheck? osName; - final StringCheck? osVersion; - final BoolCheck? diskEncryption; - final BoolCheck? antivirusPresent; - final BoolCheck? windowsAdDomainJoined; - final Int32Check? windowsSecurityUpdateAgeDays; - final StringCheck? linuxKernelVersion; - final BoolCheck? deviceIntegrity; - - const DevicePostureData({ - required this.defguardClientVersion, - required this.osType, - this.osName, - this.osVersion, - this.diskEncryption, - this.antivirusPresent, - this.windowsAdDomainJoined, - this.windowsSecurityUpdateAgeDays, - this.linuxKernelVersion, - this.deviceIntegrity, - }); - - factory DevicePostureData.fromJson(Map json) => - _$DevicePostureDataFromJson(json); - - Map toJson() => _$DevicePostureDataToJson(this); -} - -DevicePostureData getPosture() { - final notApplicable = UnavailableReason.notApplicable; - - return DevicePostureData( - defguardClientVersion: '2.1.0', - osType: 'Android', - osName: StringCheck.value('Android'), - osVersion: StringCheck.value('16'), - diskEncryption: BoolCheck.unavailable(notApplicable), - antivirusPresent: BoolCheck.unavailable(notApplicable), - windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), - windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), - linuxKernelVersion: StringCheck.unavailable(notApplicable), - deviceIntegrity: BoolCheck.value(true), - ); -} - @JsonSerializable() class StartMfaRequest { final String pubkey; diff --git a/client/lib/data/proxy/mfa.g.dart b/client/lib/data/proxy/mfa.g.dart index 526ed523..3b09b30d 100644 --- a/client/lib/data/proxy/mfa.g.dart +++ b/client/lib/data/proxy/mfa.g.dart @@ -6,138 +6,6 @@ part of 'mfa.dart'; // JsonSerializableGenerator // ************************************************************************** -StringCheck _$StringCheckFromJson(Map json) => - $checkedCreate('StringCheck', json, ($checkedConvert) { - final val = StringCheck( - result: $checkedConvert('result', (v) => v as Map), - ); - return val; - }); - -const _$StringCheckFieldMap = {'result': 'result'}; - -Map _$StringCheckToJson(StringCheck instance) => - {'result': instance.result}; - -BoolCheck _$BoolCheckFromJson(Map json) => - $checkedCreate('BoolCheck', json, ($checkedConvert) { - final val = BoolCheck( - result: $checkedConvert('result', (v) => v as Map), - ); - return val; - }); - -const _$BoolCheckFieldMap = {'result': 'result'}; - -Map _$BoolCheckToJson(BoolCheck instance) => { - 'result': instance.result, -}; - -Int32Check _$Int32CheckFromJson(Map json) => - $checkedCreate('Int32Check', json, ($checkedConvert) { - final val = Int32Check( - result: $checkedConvert('result', (v) => v as Map), - ); - return val; - }); - -const _$Int32CheckFieldMap = {'result': 'result'}; - -Map _$Int32CheckToJson(Int32Check instance) => - {'result': instance.result}; - -DevicePostureData _$DevicePostureDataFromJson( - Map json, -) => $checkedCreate( - 'DevicePostureData', - json, - ($checkedConvert) { - final val = DevicePostureData( - defguardClientVersion: $checkedConvert( - 'defguard_client_version', - (v) => v as String, - ), - osType: $checkedConvert('os_type', (v) => v as String), - osName: $checkedConvert( - 'os_name', - (v) => - v == null ? null : StringCheck.fromJson(v as Map), - ), - osVersion: $checkedConvert( - 'os_version', - (v) => - v == null ? null : StringCheck.fromJson(v as Map), - ), - diskEncryption: $checkedConvert( - 'disk_encryption', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - antivirusPresent: $checkedConvert( - 'antivirus_present', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - windowsAdDomainJoined: $checkedConvert( - 'windows_ad_domain_joined', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - windowsSecurityUpdateAgeDays: $checkedConvert( - 'windows_security_update_age_days', - (v) => - v == null ? null : Int32Check.fromJson(v as Map), - ), - linuxKernelVersion: $checkedConvert( - 'linux_kernel_version', - (v) => - v == null ? null : StringCheck.fromJson(v as Map), - ), - deviceIntegrity: $checkedConvert( - 'device_integrity', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - ); - return val; - }, - fieldKeyMap: const { - 'defguardClientVersion': 'defguard_client_version', - 'osType': 'os_type', - 'osName': 'os_name', - 'osVersion': 'os_version', - 'diskEncryption': 'disk_encryption', - 'antivirusPresent': 'antivirus_present', - 'windowsAdDomainJoined': 'windows_ad_domain_joined', - 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', - 'linuxKernelVersion': 'linux_kernel_version', - 'deviceIntegrity': 'device_integrity', - }, -); - -const _$DevicePostureDataFieldMap = { - 'defguardClientVersion': 'defguard_client_version', - 'osType': 'os_type', - 'osName': 'os_name', - 'osVersion': 'os_version', - 'diskEncryption': 'disk_encryption', - 'antivirusPresent': 'antivirus_present', - 'windowsAdDomainJoined': 'windows_ad_domain_joined', - 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', - 'linuxKernelVersion': 'linux_kernel_version', - 'deviceIntegrity': 'device_integrity', -}; - -Map _$DevicePostureDataToJson(DevicePostureData instance) => - { - 'defguard_client_version': instance.defguardClientVersion, - 'os_type': instance.osType, - 'os_name': instance.osName, - 'os_version': instance.osVersion, - 'disk_encryption': instance.diskEncryption, - 'antivirus_present': instance.antivirusPresent, - 'windows_ad_domain_joined': instance.windowsAdDomainJoined, - 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, - 'linux_kernel_version': instance.linuxKernelVersion, - 'device_integrity': instance.deviceIntegrity, - }; - StartMfaRequest _$StartMfaRequestFromJson(Map json) => $checkedCreate( 'StartMfaRequest', diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart new file mode 100644 index 00000000..18eab89d --- /dev/null +++ b/client/lib/enterprise/postures.dart @@ -0,0 +1,115 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'postures.g.dart'; + +enum UnavailableReason { + unspecified(0), + insufficientPermissions(1), + notApplicable(2), + detectionFailed(3); + + final int value; + + const UnavailableReason(this.value); +} + +@JsonSerializable() +class StringCheck { + final Map result; + + const StringCheck({required this.result}); + + factory StringCheck.value(String value) => + StringCheck(result: {'Value': value}); + + factory StringCheck.unavailable(UnavailableReason reason) => + StringCheck(result: {'Unavailable': reason.value}); + + factory StringCheck.fromJson(Map json) => + _$StringCheckFromJson(json); + + Map toJson() => _$StringCheckToJson(this); +} + +@JsonSerializable() +class BoolCheck { + final Map result; + + const BoolCheck({required this.result}); + + factory BoolCheck.value(bool value) => BoolCheck(result: {'Value': value}); + + factory BoolCheck.unavailable(UnavailableReason reason) => + BoolCheck(result: {'Unavailable': reason.value}); + + factory BoolCheck.fromJson(Map json) => + _$BoolCheckFromJson(json); + + Map toJson() => _$BoolCheckToJson(this); +} + +@JsonSerializable() +class Int32Check { + final Map result; + + const Int32Check({required this.result}); + + factory Int32Check.value(int value) => Int32Check(result: {'Value': value}); + + factory Int32Check.unavailable(UnavailableReason reason) => + Int32Check(result: {'Unavailable': reason.value}); + + factory Int32Check.fromJson(Map json) => + _$Int32CheckFromJson(json); + + Map toJson() => _$Int32CheckToJson(this); +} + +@JsonSerializable() +class DevicePostureData { + final String defguardClientVersion; + final String osType; + final StringCheck? osName; + final StringCheck? osVersion; + final BoolCheck? diskEncryption; + final BoolCheck? antivirusPresent; + final BoolCheck? windowsAdDomainJoined; + final Int32Check? windowsSecurityUpdateAgeDays; + final StringCheck? linuxKernelVersion; + final BoolCheck? deviceIntegrity; + + const DevicePostureData({ + required this.defguardClientVersion, + required this.osType, + this.osName, + this.osVersion, + this.diskEncryption, + this.antivirusPresent, + this.windowsAdDomainJoined, + this.windowsSecurityUpdateAgeDays, + this.linuxKernelVersion, + this.deviceIntegrity, + }); + + factory DevicePostureData.fromJson(Map json) => + _$DevicePostureDataFromJson(json); + + Map toJson() => _$DevicePostureDataToJson(this); +} + +DevicePostureData getPosture() { + final notApplicable = UnavailableReason.notApplicable; + + return DevicePostureData( + defguardClientVersion: '2.1.0', + osType: 'Android', + osName: StringCheck.value('Android'), + osVersion: StringCheck.value('16'), + diskEncryption: BoolCheck.unavailable(notApplicable), + antivirusPresent: BoolCheck.unavailable(notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), + windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), + linuxKernelVersion: StringCheck.unavailable(notApplicable), + deviceIntegrity: BoolCheck.value(true), + ); +} diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart new file mode 100644 index 00000000..83faf888 --- /dev/null +++ b/client/lib/enterprise/postures.g.dart @@ -0,0 +1,139 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'postures.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +StringCheck _$StringCheckFromJson(Map json) => + $checkedCreate('StringCheck', json, ($checkedConvert) { + final val = StringCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$StringCheckFieldMap = {'result': 'result'}; + +Map _$StringCheckToJson(StringCheck instance) => + {'result': instance.result}; + +BoolCheck _$BoolCheckFromJson(Map json) => + $checkedCreate('BoolCheck', json, ($checkedConvert) { + final val = BoolCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$BoolCheckFieldMap = {'result': 'result'}; + +Map _$BoolCheckToJson(BoolCheck instance) => { + 'result': instance.result, +}; + +Int32Check _$Int32CheckFromJson(Map json) => + $checkedCreate('Int32Check', json, ($checkedConvert) { + final val = Int32Check( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$Int32CheckFieldMap = {'result': 'result'}; + +Map _$Int32CheckToJson(Int32Check instance) => + {'result': instance.result}; + +DevicePostureData _$DevicePostureDataFromJson( + Map json, +) => $checkedCreate( + 'DevicePostureData', + json, + ($checkedConvert) { + final val = DevicePostureData( + defguardClientVersion: $checkedConvert( + 'defguard_client_version', + (v) => v as String, + ), + osType: $checkedConvert('os_type', (v) => v as String), + osName: $checkedConvert( + 'os_name', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + osVersion: $checkedConvert( + 'os_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + diskEncryption: $checkedConvert( + 'disk_encryption', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + antivirusPresent: $checkedConvert( + 'antivirus_present', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsAdDomainJoined: $checkedConvert( + 'windows_ad_domain_joined', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsSecurityUpdateAgeDays: $checkedConvert( + 'windows_security_update_age_days', + (v) => + v == null ? null : Int32Check.fromJson(v as Map), + ), + linuxKernelVersion: $checkedConvert( + 'linux_kernel_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + deviceIntegrity: $checkedConvert( + 'device_integrity', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', + }, +); + +const _$DevicePostureDataFieldMap = { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', +}; + +Map _$DevicePostureDataToJson(DevicePostureData instance) => + { + 'defguard_client_version': instance.defguardClientVersion, + 'os_type': instance.osType, + 'os_name': instance.osName, + 'os_version': instance.osVersion, + 'disk_encryption': instance.diskEncryption, + 'antivirus_present': instance.antivirusPresent, + 'windows_ad_domain_joined': instance.windowsAdDomainJoined, + 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, + 'linux_kernel_version': instance.linuxKernelVersion, + 'device_integrity': instance.deviceIntegrity, + }; diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index 97e1137e..e7a3893c 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:mobile/data/db/database.dart'; import 'package:mobile/data/proxy/mfa.dart'; +import 'package:mobile/enterprise/postures.dart'; import 'package:mobile/enterprise/screens/mfa/openid_mfa_screen.dart'; import 'package:mobile/open/api.dart'; import 'package:mobile/data/plugin/plugin.dart'; From dcebbf663a313d237ac4b8fb70133df6d9aa91e2 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 10:07:39 +0200 Subject: [PATCH 21/65] gather real posture report --- client/lib/enterprise/postures.dart | 87 ++++++++++++++++--- .../instance/services/tunnel_service.dart | 2 +- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index 18eab89d..4fa63eaf 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -1,4 +1,8 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:package_info_plus/package_info_plus.dart'; part 'postures.g.dart'; @@ -97,19 +101,76 @@ class DevicePostureData { Map toJson() => _$DevicePostureDataToJson(this); } -DevicePostureData getPosture() { - final notApplicable = UnavailableReason.notApplicable; - +Future getPosture() async { + final packageInfo = await PackageInfo.fromPlatform(); + final deviceInfo = DeviceInfoPlugin(); + + // Handle Android + if (Platform.isAndroid) { + final android = await deviceInfo.androidInfo; + return DevicePostureData( + defguardClientVersion: packageInfo.version, + osType: "Android", + osName: StringCheck.value(android.version.release), + osVersion: StringCheck.value(android.version.release), + // TODO + deviceIntegrity: BoolCheck.value(true), + + diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), + antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable( + UnavailableReason.notApplicable, + ), + windowsSecurityUpdateAgeDays: Int32Check.unavailable( + UnavailableReason.notApplicable, + ), + linuxKernelVersion: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), + ); + } + + // Handle iOS + if (Platform.isIOS) { + final ios = await deviceInfo.iosInfo; + return DevicePostureData( + defguardClientVersion: packageInfo.version, + osType: "iOS", + osName: StringCheck.value(ios.systemName), + osVersion: StringCheck.value(ios.systemVersion), + deviceIntegrity: BoolCheck.unavailable(UnavailableReason.notApplicable), + + diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), + antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable( + UnavailableReason.notApplicable, + ), + windowsSecurityUpdateAgeDays: Int32Check.unavailable( + UnavailableReason.notApplicable, + ), + linuxKernelVersion: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), + ); + } + + // Fallback for unsupported platforms: report the generic Dart OS values return DevicePostureData( - defguardClientVersion: '2.1.0', - osType: 'Android', - osName: StringCheck.value('Android'), - osVersion: StringCheck.value('16'), - diskEncryption: BoolCheck.unavailable(notApplicable), - antivirusPresent: BoolCheck.unavailable(notApplicable), - windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), - windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), - linuxKernelVersion: StringCheck.unavailable(notApplicable), - deviceIntegrity: BoolCheck.value(true), + defguardClientVersion: packageInfo.version, + osType: Platform.operatingSystem, + osName: StringCheck.value(Platform.operatingSystem), + osVersion: StringCheck.unavailable(UnavailableReason.unspecified), + diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), + antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable( + UnavailableReason.notApplicable, + ), + windowsSecurityUpdateAgeDays: Int32Check.unavailable( + UnavailableReason.notApplicable, + ), + linuxKernelVersion: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), + deviceIntegrity: BoolCheck.unavailable(UnavailableReason.notApplicable), ); } diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index e7a3893c..d39dc843 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -292,7 +292,7 @@ class TunnelService { talker.debug( "Starting MFA for networkId: $networkId, method: ${method.toReadableString()}", ); - final postureData = postureCheckRequired ? getPosture() : null; + final postureData = postureCheckRequired ? await getPosture() : null; final request = StartMfaRequest( pubkey: pubkey, locationId: networkId, From f06a287301804aba85f09cc3d5ee9b5d58d42d48 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 11:57:23 +0200 Subject: [PATCH 22/65] comment --- client/lib/enterprise/postures.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index 4fa63eaf..36a4070f 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -113,8 +113,9 @@ Future getPosture() async { osType: "Android", osName: StringCheck.value(android.version.release), osVersion: StringCheck.value(android.version.release), - // TODO - deviceIntegrity: BoolCheck.value(true), + // TODO: implement full google play integrity check flow + // TODO: https://github.com/DefGuard/defguard/issues/2986 + deviceIntegrity: BoolCheck.unavailable(UnavailableReason.unspecified), diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), From c7a8ff7b8343a3311ef5ba42860a37911239a99c Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 14:32:23 +0200 Subject: [PATCH 23/65] posture-check-only connection flow --- client/lib/enterprise/postures.dart | 30 ++++++++++ client/lib/enterprise/postures.g.dart | 58 ++++++++++++++++++ client/lib/open/api.dart | 43 ++++++++++++++ .../instance/services/tunnel_service.dart | 59 +++++++++++++++++++ 4 files changed, 190 insertions(+) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index 36a4070f..d227f3af 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -101,6 +101,36 @@ class DevicePostureData { Map toJson() => _$DevicePostureDataToJson(this); } +@JsonSerializable() +class PostureConnectRequest { + final int locationId; + final String pubkey; + final DevicePostureData devicePostureData; + + const PostureConnectRequest({ + required this.locationId, + required this.pubkey, + required this.devicePostureData, + }); + + factory PostureConnectRequest.fromJson(Map json) => + _$PostureConnectRequestFromJson(json); + + Map toJson() => _$PostureConnectRequestToJson(this); +} + +@JsonSerializable() +class PostureConnectResponse { + final String presharedKey; + + const PostureConnectResponse({required this.presharedKey}); + + factory PostureConnectResponse.fromJson(Map json) => + _$PostureConnectResponseFromJson(json); + + Map toJson() => _$PostureConnectResponseToJson(this); +} + Future getPosture() async { final packageInfo = await PackageInfo.fromPlatform(); final deviceInfo = DeviceInfoPlugin(); diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart index 83faf888..f25bdc61 100644 --- a/client/lib/enterprise/postures.g.dart +++ b/client/lib/enterprise/postures.g.dart @@ -137,3 +137,61 @@ Map _$DevicePostureDataToJson(DevicePostureData instance) => 'linux_kernel_version': instance.linuxKernelVersion, 'device_integrity': instance.deviceIntegrity, }; + +PostureConnectRequest _$PostureConnectRequestFromJson( + Map json, +) => $checkedCreate( + 'PostureConnectRequest', + json, + ($checkedConvert) { + final val = PostureConnectRequest( + locationId: $checkedConvert('location_id', (v) => (v as num).toInt()), + pubkey: $checkedConvert('pubkey', (v) => v as String), + devicePostureData: $checkedConvert( + 'device_posture_data', + (v) => DevicePostureData.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'locationId': 'location_id', + 'devicePostureData': 'device_posture_data', + }, +); + +const _$PostureConnectRequestFieldMap = { + 'locationId': 'location_id', + 'pubkey': 'pubkey', + 'devicePostureData': 'device_posture_data', +}; + +Map _$PostureConnectRequestToJson( + PostureConnectRequest instance, +) => { + 'location_id': instance.locationId, + 'pubkey': instance.pubkey, + 'device_posture_data': instance.devicePostureData, +}; + +PostureConnectResponse _$PostureConnectResponseFromJson( + Map json, +) => $checkedCreate( + 'PostureConnectResponse', + json, + ($checkedConvert) { + final val = PostureConnectResponse( + presharedKey: $checkedConvert('preshared_key', (v) => v as String), + ); + return val; + }, + fieldKeyMap: const {'presharedKey': 'preshared_key'}, +); + +const _$PostureConnectResponseFieldMap = { + 'presharedKey': 'preshared_key', +}; + +Map _$PostureConnectResponseToJson( + PostureConnectResponse instance, +) => {'preshared_key': instance.presharedKey}; diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 14168455..cd03318f 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -9,6 +9,7 @@ import 'package:native_dio_adapter/native_dio_adapter.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; +import 'package:mobile/enterprise/postures.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:mobile/data/proxy/mfa.dart'; @@ -20,6 +21,16 @@ import '../logging.dart'; const _apiV1Segments = ['api', 'v1']; final enrollmentPathSegments = ['api', 'v1', 'enrollment']; final mfaPathSegments = ['api', 'v1', 'client-mfa']; +final posturePathSegments = ['api', 'v1', 'posture']; + +class PostureCheckException implements Exception { + final String message; + + const PostureCheckException(this.message); + + @override + String toString() => 'Posture error: $message'; +} class MfaMethodNotAvailableException implements Exception { final MfaMethod method; @@ -213,6 +224,38 @@ class _ProxyApi { } } + Future postureConnect( + Uri url, + PostureConnectRequest data, + ) async { + final endpoint = url.replace( + pathSegments: [...url.pathSegments, ...posturePathSegments, 'connect'], + ); + + try { + final response = await _dio.postUri(endpoint, data: data.toJson()); + return PostureConnectResponse.fromJson(response.data); + } on DioException catch (e) { + final responseData = e.response?.data; + final dataError = responseData is Map + ? responseData['error'] + : null; + if (e.response?.statusCode == 403 && dataError is String) { + throw PostureCheckException(dataError); + } + if (e.response != null) { + throw HttpException( + 'Failed to perform posture check. Status: ${e.response?.statusCode} Body: ${e.response?.data}', + ); + } + rethrow; + } catch (e) { + throw FormatException( + 'Invalid JSON sent by posture check endpoint! Error: $e', + ); + } + } + Future finishMfa(Uri url, FinishMfaRequest data) async { final endpoint = url.replace( pathSegments: [...url.pathSegments, ...mfaPathSegments, 'finish'], diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index d39dc843..664eef10 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -120,6 +120,16 @@ class TunnelService { return; } payload.presharedKey = presharedKey; + } else if (payload.postureCheckRequired) { + final presharedKey = await _performPostureCheck( + navigator: navigator, + proxyUrl: instance.proxyUrl, + payload: payload, + ); + if (presharedKey == null) { + return; + } + payload.presharedKey = presharedKey; } // start the tunnel @@ -134,6 +144,38 @@ class TunnelService { location.locationMfaMode == LocationMfaMode.external; } + /// Performs posture-only authorization and returns runtime preshared key. + static Future _performPostureCheck({ + required NavigatorState navigator, + required String proxyUrl, + required PluginConnectPayload payload, + }) async { + final messenger = ScaffoldMessenger.of(navigator.context); + try { + return await _authorizePostureOnly( + proxyUrl, + payload.devicePublicKey, + payload.networkId, + ); + } on PostureCheckException catch (e) { + talker.error('Posture check failed', e); + messenger.showSnackBar( + dgSnackBar(text: e.toString(), textColor: DgColor.textAlert), + ); + } on HttpException catch (e) { + talker.error('Posture check request failed', e); + messenger.showSnackBar( + dgSnackBar(text: 'Error: ${e.message}', textColor: DgColor.textAlert), + ); + } catch (e) { + talker.error('Posture-only connect failed: $e'); + messenger.showSnackBar( + dgSnackBar(text: 'Error: $e', textColor: DgColor.textAlert), + ); + } + return null; + } + /// Performs MFA using specified method. /// Returns preshared key. static Future _performMfa({ @@ -304,6 +346,23 @@ class TunnelService { return await proxyApi.startMfa(uri, request); } + /// Calls `/posture/connect` endpoint and returns runtime preshared key. + static Future _authorizePostureOnly( + String url, + String pubkey, + int networkId, + ) async { + talker.debug('Starting posture check for networkId: $networkId'); + final request = PostureConnectRequest( + locationId: networkId, + pubkey: pubkey, + devicePostureData: await getPosture(), + ); + + final response = await proxyApi.postureConnect(Uri.parse(url), request); + return response.presharedKey; + } + /// Prepares wireguard plugin configuration static PluginConnectPayload _makePayload( DefguardInstance instance, From 5f4916ec5e1e4da975bd3deba9bfeb65b107db27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 19 Jun 2026 15:16:15 +0200 Subject: [PATCH 24/65] WhatsNew for TestFlight --- .github/workflows/build.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cddbe409..9b261044 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -54,8 +54,11 @@ jobs: - name: Build iOS run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} + - name: Get last commit message + run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV + - name: Upload app to TestFlight - uses: apple-actions/upload-testflight-build@v3 + uses: apple-actions/upload-testflight-build@v5 # Mobile applications are published to the App Store manually, with release tags applied # post-publication. To avoid redundant uploads, this step executes only for non-tagged # builds, ensuring tagged releases are distributed exclusively to GitHub. @@ -65,6 +68,7 @@ jobs: issuer-id: ${{ secrets.API_ISSUER_ID }} api-key-id: ${{ secrets.ASC_API_KEY_ID }} api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} + release-notes: ${{ env.COMMIT_MSG }} build-android: runs-on: [self-hosted, macOS] From 19613cdd30ea5be409cbba68afa357305bb2ec94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 19 Jun 2026 15:25:42 +0200 Subject: [PATCH 25/65] Bump version --- client/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- client/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 97f5ddc9..3309e0e5 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -644,7 +644,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.6.4; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -698,7 +698,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.6.4; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -749,7 +749,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.6.4; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 54384945..ac44d0c0 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.3+1 +version: 1.6.4+1 environment: sdk: ^3.8.1 From 64ea8cd2b2540b70f0e44e8a9cdb969ce1eabd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:46:26 +0200 Subject: [PATCH 26/65] add security level patch posture check data (#192) * Update postures.dart * fix posture error display, ran dart format --- .fvmrc | 3 ++ client/lib/data/db/enums.dart | 3 +- client/lib/data/proxy/config.dart | 6 ++-- client/lib/enterprise/config_update.dart | 27 +++++++----------- client/lib/enterprise/postures.dart | 11 ++++++++ client/lib/enterprise/postures.g.dart | 8 ++++++ client/lib/logging.dart | 2 +- client/lib/open/api.dart | 25 +++++++++++------ .../lib/open/riverpod/biometrics_state.dart | 1 - .../riverpod/package_info/package_info.dart | 2 +- client/lib/open/riverpod/plugin/plugin.dart | 2 +- .../add_instance/generate_wireguard.dart | 6 +--- .../screens/instance/instance_screen.dart | 28 +++++++++---------- .../widgets/delete_instance_dialog.dart | 9 +++--- .../open/widgets/buttons/dg_text_button.dart | 5 +++- client/lib/open/widgets/dg_checkbox.dart | 2 +- client/lib/open/widgets/dg_menu.dart | 12 +++++--- client/lib/open/widgets/loading_screen.dart | 2 +- client/lib/router/routes.dart | 27 ++++++------------ client/lib/theme/color.dart | 2 +- client/lib/theme/text.dart | 2 +- client/lib/utils/position.dart | 11 +++----- client/lib/utils/safe_insets.dart | 16 ++++++----- client/lib/utils/update_instance.dart | 5 ++-- client/pubspec.yaml | 2 +- 25 files changed, 118 insertions(+), 101 deletions(-) create mode 100644 .fvmrc diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 00000000..ac62dd32 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.38.10" +} \ No newline at end of file diff --git a/client/lib/data/db/enums.dart b/client/lib/data/db/enums.dart index 36b4c072..85c264e2 100644 --- a/client/lib/data/db/enums.dart +++ b/client/lib/data/db/enums.dart @@ -101,7 +101,8 @@ enum ClientTrafficPolicy { ClientTrafficPolicy.values.firstWhere((e) => e.value == value); } -class ClientTrafficPolicyConverter extends TypeConverter { +class ClientTrafficPolicyConverter + extends TypeConverter { const ClientTrafficPolicyConverter(); @override diff --git a/client/lib/data/proxy/config.dart b/client/lib/data/proxy/config.dart index 3bc931de..ffdb4d11 100644 --- a/client/lib/data/proxy/config.dart +++ b/client/lib/data/proxy/config.dart @@ -49,8 +49,8 @@ class NetworkInfoResponse { this.token, }); - factory NetworkInfoResponse.fromJson(Map json) => - _$NetworkInfoResponseFromJson(json); + factory NetworkInfoResponse.fromJson(Map json) => + _$NetworkInfoResponseFromJson(json); - Map toJson() => _$NetworkInfoResponseToJson(this); + Map toJson() => _$NetworkInfoResponseToJson(this); } diff --git a/client/lib/enterprise/config_update.dart b/client/lib/enterprise/config_update.dart index f7778916..d2360f6c 100644 --- a/client/lib/enterprise/config_update.dart +++ b/client/lib/enterprise/config_update.dart @@ -44,8 +44,7 @@ class ConfigurationUpdater extends HookConsumerWidget { ); for (final instance in instances) { talker.debug( - "Auto configuration update started for ${instance.name} (${instance - .id})", + "Auto configuration update started for ${instance.name} (${instance.id})", ); final (responseData, responseStatus, headers) = await proxyApi .pollConfiguration(instance.proxyUrl, instance.poolingToken); @@ -61,8 +60,7 @@ class ConfigurationUpdater extends HookConsumerWidget { headers['defguard-component-version']?.first; if (coreVersionStr == null || proxyVersionStr == null) { talker.error( - "Version headers missing for ${instance - .logName}, treating as unsupported", + "Version headers missing for ${instance.logName}, treating as unsupported", ); versionUnsupportedInstances.add({ 'name': instance.name, @@ -95,9 +93,7 @@ class ConfigurationUpdater extends HookConsumerWidget { } if (responseData == null) { talker.error( - "Auto configuration update failed for ${instance - .logName} ! Update data retrieval failed, status: ${responseStatus ?? - "unknown"}!", + "Auto configuration update failed for ${instance.logName} ! Update data retrieval failed, status: ${responseStatus ?? "unknown"}!", ); continue; } @@ -112,12 +108,7 @@ class ConfigurationUpdater extends HookConsumerWidget { ); if (updateResult != null) { talker.info( - "Instance ${instance - .logName} results: Instance updated: ${updateResult - .instanceChanged} | Locations updated: ${updateResult - .locationsUpdated} | Locations removed: ${updateResult - .locationsRemoved} | Locations added: ${updateResult - .locationsAdded}", + "Instance ${instance.logName} results: Instance updated: ${updateResult.instanceChanged} | Locations updated: ${updateResult.locationsUpdated} | Locations removed: ${updateResult.locationsRemoved} | Locations added: ${updateResult.locationsAdded}", ); if (updateResult.didChange) { final message = getInstanceUpdateMessage( @@ -144,7 +135,7 @@ class ConfigurationUpdater extends HookConsumerWidget { "The following instances have versions that are incompatible with your Defguard Mobile Client and may not work correctly:\n\n"; for (final instance in versionUnsupportedInstances) { message += - "- ${instance['name']}: Defguard Core ${instance['coreVersion']} (expected >=$supportedCoreVersion), Defguard Proxy ${instance['proxyVersion']} (expected >=$supportedProxyVersion)\n"; + "- ${instance['name']}: Defguard Core ${instance['coreVersion']} (expected >=$supportedCoreVersion), Defguard Proxy ${instance['proxyVersion']} (expected >=$supportedProxyVersion)\n"; } message += "\nPlease contact your administrator."; toaster.showInfo( @@ -170,10 +161,12 @@ class ConfigurationUpdater extends HookConsumerWidget { // update when user wakes up application useEffect(() { final timeTick = DateTime.now(); - final afterCooldown = lastConfigUpdate.value == null || + final afterCooldown = + lastConfigUpdate.value == null || (lastConfigUpdate.value != null && - lastConfigUpdate.value!.add(Duration(seconds: 60)).isBefore( - timeTick)); + lastConfigUpdate.value! + .add(Duration(seconds: 60)) + .isBefore(timeTick)); if (lifecycle == AppLifecycleState.resumed && afterCooldown) { lastConfigUpdate.value = timeTick; updateConfiguration(); diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index d227f3af..f40d3e0a 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -81,6 +81,7 @@ class DevicePostureData { final Int32Check? windowsSecurityUpdateAgeDays; final StringCheck? linuxKernelVersion; final BoolCheck? deviceIntegrity; + final StringCheck? androidSecurityPatchDate; const DevicePostureData({ required this.defguardClientVersion, @@ -93,6 +94,7 @@ class DevicePostureData { this.windowsSecurityUpdateAgeDays, this.linuxKernelVersion, this.deviceIntegrity, + this.androidSecurityPatchDate, }); factory DevicePostureData.fromJson(Map json) => @@ -158,6 +160,9 @@ Future getPosture() async { linuxKernelVersion: StringCheck.unavailable( UnavailableReason.notApplicable, ), + androidSecurityPatchDate: android.version.securityPatch != null + ? StringCheck.value(android.version.securityPatch!) + : StringCheck.unavailable(UnavailableReason.detectionFailed), ); } @@ -182,6 +187,9 @@ Future getPosture() async { linuxKernelVersion: StringCheck.unavailable( UnavailableReason.notApplicable, ), + androidSecurityPatchDate: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), ); } @@ -203,5 +211,8 @@ Future getPosture() async { UnavailableReason.notApplicable, ), deviceIntegrity: BoolCheck.unavailable(UnavailableReason.notApplicable), + androidSecurityPatchDate: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), ); } diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart index f25bdc61..b2903e4d 100644 --- a/client/lib/enterprise/postures.g.dart +++ b/client/lib/enterprise/postures.g.dart @@ -94,6 +94,11 @@ DevicePostureData _$DevicePostureDataFromJson( 'device_integrity', (v) => v == null ? null : BoolCheck.fromJson(v as Map), ), + androidSecurityPatchDate: $checkedConvert( + 'android_security_patch_date', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), ); return val; }, @@ -108,6 +113,7 @@ DevicePostureData _$DevicePostureDataFromJson( 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', 'linuxKernelVersion': 'linux_kernel_version', 'deviceIntegrity': 'device_integrity', + 'androidSecurityPatchDate': 'android_security_patch_date', }, ); @@ -122,6 +128,7 @@ const _$DevicePostureDataFieldMap = { 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', 'linuxKernelVersion': 'linux_kernel_version', 'deviceIntegrity': 'device_integrity', + 'androidSecurityPatchDate': 'android_security_patch_date', }; Map _$DevicePostureDataToJson(DevicePostureData instance) => @@ -136,6 +143,7 @@ Map _$DevicePostureDataToJson(DevicePostureData instance) => 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, 'linux_kernel_version': instance.linuxKernelVersion, 'device_integrity': instance.deviceIntegrity, + 'android_security_patch_date': instance.androidSecurityPatchDate, }; PostureConnectRequest _$PostureConnectRequestFromJson( diff --git a/client/lib/logging.dart b/client/lib/logging.dart index f1284bb6..8d268811 100644 --- a/client/lib/logging.dart +++ b/client/lib/logging.dart @@ -1,3 +1,3 @@ import 'package:talker_flutter/talker_flutter.dart'; -final talker = TalkerFlutter.init(); \ No newline at end of file +final talker = TalkerFlutter.init(); diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index cd03318f..2f46fbbe 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -5,15 +5,14 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; -import 'package:native_dio_adapter/native_dio_adapter.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; -import 'package:mobile/enterprise/postures.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:mobile/data/proxy/mfa.dart'; - +import 'package:mobile/enterprise/postures.dart'; +import 'package:native_dio_adapter/native_dio_adapter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart'; import '../logging.dart'; @@ -211,6 +210,13 @@ class _ProxyApi { dataError.toLowerCase().trim() == missingMFAMethodError) { throw MfaMethodNotAvailableException(data.method); } + + if (e.response?.statusCode == 403) { + final error = responseData['error'] ?? responseData['message']; + if (error is String) { + throw HttpException(error); + } + } } throw HttpException( "Failed to start MFA. Status: ${e.response?.statusCode} Body: ${e.response?.data}", @@ -237,11 +243,12 @@ class _ProxyApi { return PostureConnectResponse.fromJson(response.data); } on DioException catch (e) { final responseData = e.response?.data; - final dataError = responseData is Map - ? responseData['error'] - : null; - if (e.response?.statusCode == 403 && dataError is String) { - throw PostureCheckException(dataError); + if (e.response?.statusCode == 403 && + responseData is Map) { + final error = responseData['error'] ?? responseData['message']; + if (error is String) { + throw PostureCheckException(error); + } } if (e.response != null) { throw HttpException( diff --git a/client/lib/open/riverpod/biometrics_state.dart b/client/lib/open/riverpod/biometrics_state.dart index 8b5ddd6f..7c5421a8 100644 --- a/client/lib/open/riverpod/biometrics_state.dart +++ b/client/lib/open/riverpod/biometrics_state.dart @@ -7,7 +7,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'biometrics_state.g.dart'; - class BiometricsState { bool isSupported; bool canCheck; diff --git a/client/lib/open/riverpod/package_info/package_info.dart b/client/lib/open/riverpod/package_info/package_info.dart index bf6a28e7..d46691ab 100644 --- a/client/lib/open/riverpod/package_info/package_info.dart +++ b/client/lib/open/riverpod/package_info/package_info.dart @@ -10,4 +10,4 @@ Future packageInfo(Ref ref) async { WidgetsFlutterBinding.ensureInitialized(); final info = await PackageInfo.fromPlatform(); return info; -} \ No newline at end of file +} diff --git a/client/lib/open/riverpod/plugin/plugin.dart b/client/lib/open/riverpod/plugin/plugin.dart index 2d7b1779..4f2f6e01 100644 --- a/client/lib/open/riverpod/plugin/plugin.dart +++ b/client/lib/open/riverpod/plugin/plugin.dart @@ -15,4 +15,4 @@ class PluginActiveTunnelState extends _$PluginActiveTunnelState { void clear() { state = null; } -} \ No newline at end of file +} diff --git a/client/lib/open/screens/add_instance/generate_wireguard.dart b/client/lib/open/screens/add_instance/generate_wireguard.dart index f9cd791d..61c7d5d4 100644 --- a/client/lib/open/screens/add_instance/generate_wireguard.dart +++ b/client/lib/open/screens/add_instance/generate_wireguard.dart @@ -3,14 +3,10 @@ import 'dart:convert'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:x25519/x25519.dart' as x; - Future generateWireguardKeyPair() async { final keyPair = x.generateKeyPair(); final encodedPriv = base64Encode(keyPair.privateKey); final encodedPub = base64Encode(keyPair.publicKey); - return WireguardEncodedKeyPair( - privKey: encodedPriv, - pubKey: encodedPub, - ); + return WireguardEncodedKeyPair(privKey: encodedPriv, pubKey: encodedPub); } diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart index a725ec56..310acefd 100644 --- a/client/lib/open/screens/instance/instance_screen.dart +++ b/client/lib/open/screens/instance/instance_screen.dart @@ -415,20 +415,20 @@ class _LocationItem extends HookConsumerWidget { ); }, ), - if (instance.clientTrafficPolicy == ClientTrafficPolicy.none) - DgMenuItem( - text: "Select Traffic Routing", - onTap: () { - showDialog( - context: context, - builder: (_) => RoutingMethodDialog( - location: location, - intention: RoutingMethodDialogIntention.save, - clientTrafficPolicy: instance.clientTrafficPolicy, - ), - ); - }, - ), + if (instance.clientTrafficPolicy == ClientTrafficPolicy.none) + DgMenuItem( + text: "Select Traffic Routing", + onTap: () { + showDialog( + context: context, + builder: (_) => RoutingMethodDialog( + location: location, + intention: RoutingMethodDialogIntention.save, + clientTrafficPolicy: instance.clientTrafficPolicy, + ), + ); + }, + ), ]; }, [location, instance]); diff --git a/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart b/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart index 26976d69..e60477dc 100644 --- a/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart +++ b/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart @@ -10,7 +10,6 @@ import 'package:mobile/utils/secure_storage.dart'; import '../../../services/snackbar_service.dart'; - class DeleteInstanceDialog extends HookConsumerWidget { final DefguardInstance instance; @@ -25,7 +24,7 @@ class DeleteInstanceDialog extends HookConsumerWidget { Future deleteInstance(BuildContext context) async { try { - if(instance.mfaKeysStored) { + if (instance.mfaKeysStored) { await removeInstanceStorage(instance.secureStorageKey); } await db.managers.defguardInstances @@ -35,8 +34,10 @@ class DeleteInstanceDialog extends HookConsumerWidget { SnackbarService.show("Instance deleted"); Navigator.of(context).pop(); } - } catch(e) { - talker.error("Failed to delete instance ${instance.logName}! Reason: \n $e"); + } catch (e) { + talker.error( + "Failed to delete instance ${instance.logName}! Reason: \n $e", + ); } } diff --git a/client/lib/open/widgets/buttons/dg_text_button.dart b/client/lib/open/widgets/buttons/dg_text_button.dart index 72d43ad0..297078fe 100644 --- a/client/lib/open/widgets/buttons/dg_text_button.dart +++ b/client/lib/open/widgets/buttons/dg_text_button.dart @@ -39,7 +39,10 @@ class DgTextButton extends StatelessWidget { child: Text( text, textAlign: TextAlign.center, - style: textStyle.copyWith(decoration: TextDecoration.underline, decorationColor: textStyle.color), + style: textStyle.copyWith( + decoration: TextDecoration.underline, + decorationColor: textStyle.color, + ), ), ), ), diff --git a/client/lib/open/widgets/dg_checkbox.dart b/client/lib/open/widgets/dg_checkbox.dart index 90a7d5eb..a71a950c 100644 --- a/client/lib/open/widgets/dg_checkbox.dart +++ b/client/lib/open/widgets/dg_checkbox.dart @@ -31,7 +31,7 @@ class DgCheckbox extends StatelessWidget { Widget _getBody() { final icon = DgIconCheckbox(size: iconSize, variant: _getIconVariant()); TextStyle textStyleInner; - if(textStyle == null) { + if (textStyle == null) { textStyleInner = DgText.modal1.copyWith(color: DgColor.textBodySecondary); } else { textStyleInner = textStyle!; diff --git a/client/lib/open/widgets/dg_menu.dart b/client/lib/open/widgets/dg_menu.dart index 651d95e8..f6d3c478 100644 --- a/client/lib/open/widgets/dg_menu.dart +++ b/client/lib/open/widgets/dg_menu.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -31,7 +30,9 @@ class DgMenu extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final topOffset = useMemoized(() => anchorGeometry.position.dy + 10 + anchorGeometry.size.height); + final topOffset = useMemoized( + () => anchorGeometry.position.dy + 10 + anchorGeometry.size.height, + ); final leftOffset = useMemoized(() => anchorGeometry.position.dx); final animationController = useAnimationController( duration: 100.ms, @@ -67,8 +68,11 @@ class DgMenu extends HookConsumerWidget { builder: (context, _) => FadeTransition( opacity: animationController, child: SlideTransition( - position: Tween(begin: Offset(0, -0.05), end: Offset.zero) - .animate( + position: + Tween( + begin: Offset(0, -0.05), + end: Offset.zero, + ).animate( CurvedAnimation( parent: animationController, curve: Curves.easeOut, diff --git a/client/lib/open/widgets/loading_screen.dart b/client/lib/open/widgets/loading_screen.dart index a5e1bc5f..4ed189a6 100644 --- a/client/lib/open/widgets/loading_screen.dart +++ b/client/lib/open/widgets/loading_screen.dart @@ -29,7 +29,7 @@ class LoadingView extends StatelessWidget { mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ - DgCircularProgress(color: DgColor.iconSecondary, size: 92), + DgCircularProgress(color: DgColor.iconSecondary, size: 92), ], ), ); diff --git a/client/lib/router/routes.dart b/client/lib/router/routes.dart index 3994ccd3..c7c4f228 100644 --- a/client/lib/router/routes.dart +++ b/client/lib/router/routes.dart @@ -21,8 +21,7 @@ part 'routes.g.dart'; @TypedGoRoute(path: "/process_qr") @immutable -class ProcessQrScreenRoute extends GoRouteData - with _$ProcessQrScreenRoute { +class ProcessQrScreenRoute extends GoRouteData with _$ProcessQrScreenRoute { const ProcessQrScreenRoute(this.$extra); final ProcessQrScreenData $extra; @@ -35,8 +34,7 @@ class ProcessQrScreenRoute extends GoRouteData @TypedGoRoute(path: '/') @immutable -class HomeScreenRoute extends GoRouteData - with _$HomeScreenRoute { +class HomeScreenRoute extends GoRouteData with _$HomeScreenRoute { const HomeScreenRoute(); @override @@ -47,8 +45,7 @@ class HomeScreenRoute extends GoRouteData @TypedGoRoute(path: "/qr") @immutable -class QRScreenRoute extends GoRouteData - with _$QRScreenRoute { +class QRScreenRoute extends GoRouteData with _$QRScreenRoute { const QRScreenRoute(this.$extra); final QrScreenData $extra; @@ -61,8 +58,7 @@ class QRScreenRoute extends GoRouteData @TypedGoRoute(path: "/instance/:id") @immutable -class InstanceScreenRoute extends GoRouteData - with _$InstanceScreenRoute { +class InstanceScreenRoute extends GoRouteData with _$InstanceScreenRoute { final String id; const InstanceScreenRoute({required this.id}); @@ -75,8 +71,7 @@ class InstanceScreenRoute extends GoRouteData @TypedGoRoute(path: "/add_instance/name_device") @immutable -class NameDeviceScreenRoute extends GoRouteData - with _$NameDeviceScreenRoute { +class NameDeviceScreenRoute extends GoRouteData with _$NameDeviceScreenRoute { const NameDeviceScreenRoute(this.$extra); final NameDeviceScreenData $extra; @@ -99,8 +94,7 @@ class AddInstanceFormScreenRoute extends GoRouteData @TypedGoRoute(path: '/add_instance/init') @immutable -class AddInstanceScreenRoute extends GoRouteData - with _$AddInstanceScreenRoute { +class AddInstanceScreenRoute extends GoRouteData with _$AddInstanceScreenRoute { const AddInstanceScreenRoute(); @override @@ -111,8 +105,7 @@ class AddInstanceScreenRoute extends GoRouteData @TypedGoRoute(path: "/talker") @immutable -class TalkerScreenRoute extends GoRouteData - with _$TalkerScreenRoute { +class TalkerScreenRoute extends GoRouteData with _$TalkerScreenRoute { @override Widget build(BuildContext context, GoRouterState state) { return TalkerScreen(talker: talker); @@ -121,8 +114,7 @@ class TalkerScreenRoute extends GoRouteData @TypedGoRoute(path: "/mfa/openid") @immutable -class OpenIdMfaScreenRoute extends GoRouteData - with _$OpenIdMfaScreenRoute { +class OpenIdMfaScreenRoute extends GoRouteData with _$OpenIdMfaScreenRoute { const OpenIdMfaScreenRoute(this.$extra); final OpenIdMfaScreenData $extra; @@ -149,8 +141,7 @@ class OpenIdMfaWaitingScreenRoute extends GoRouteData @TypedGoRoute(path: "/mfa/code") @immutable -class MfaCodeScreenRoute extends GoRouteData - with _$MfaCodeScreenRoute { +class MfaCodeScreenRoute extends GoRouteData with _$MfaCodeScreenRoute { const MfaCodeScreenRoute(this.$extra); final MfaCodeScreenData $extra; diff --git a/client/lib/theme/color.dart b/client/lib/theme/color.dart index dac138c1..b797de58 100644 --- a/client/lib/theme/color.dart +++ b/client/lib/theme/color.dart @@ -81,4 +81,4 @@ final BoxShadow dgBoxShadow = BoxShadow( offset: Offset(0, 12), blurRadius: 24, spreadRadius: 0, -); \ No newline at end of file +); diff --git a/client/lib/theme/text.dart b/client/lib/theme/text.dart index 2e37da85..484c6dd8 100644 --- a/client/lib/theme/text.dart +++ b/client/lib/theme/text.dart @@ -25,7 +25,7 @@ class DgText { static const TextStyle body2 = TextStyle( fontFamily: _poppins, fontWeight: FontWeight.w400, - fontSize: 15 + fontSize: 15, ); static const TextStyle modal1 = TextStyle( fontFamily: _roboto, diff --git a/client/lib/utils/position.dart b/client/lib/utils/position.dart index c09f7236..4984d53c 100644 --- a/client/lib/utils/position.dart +++ b/client/lib/utils/position.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; Offset? getRenderObjectPosition(RenderObject? renderObject) { - if(renderObject is RenderBox && renderObject.hasSize) { + if (renderObject is RenderBox && renderObject.hasSize) { return renderObject.localToGlobal(Offset.zero); } return null; @@ -11,18 +11,15 @@ class WidgetGeometry { final Offset position; final Size size; - const WidgetGeometry({ - required this.position, - required this.size, - }); + const WidgetGeometry({required this.position, required this.size}); static WidgetGeometry fromKey(GlobalKey key) { final renderObject = key.currentContext?.findRenderObject(); - if(renderObject is RenderBox && renderObject.hasSize) { + if (renderObject is RenderBox && renderObject.hasSize) { final position = renderObject.localToGlobal(Offset.zero); final size = renderObject.size; return WidgetGeometry(position: position, size: size); } return WidgetGeometry(position: Offset.zero, size: Size.zero); } -} \ No newline at end of file +} diff --git a/client/lib/utils/safe_insets.dart b/client/lib/utils/safe_insets.dart index 6bd86a29..cb9b9b74 100644 --- a/client/lib/utils/safe_insets.dart +++ b/client/lib/utils/safe_insets.dart @@ -1,11 +1,13 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; -(double, double) safeInsetHorizontal(BuildContext context, - double preferredPadding) { - final safe = MediaQuery - .of(context) - .padding; - return (math.max(safe.left, preferredPadding), math.max( - safe.right, preferredPadding)); +(double, double) safeInsetHorizontal( + BuildContext context, + double preferredPadding, +) { + final safe = MediaQuery.of(context).padding; + return ( + math.max(safe.left, preferredPadding), + math.max(safe.right, preferredPadding), + ); } diff --git a/client/lib/utils/update_instance.dart b/client/lib/utils/update_instance.dart index 81b079fb..52804943 100644 --- a/client/lib/utils/update_instance.dart +++ b/client/lib/utils/update_instance.dart @@ -60,7 +60,8 @@ Future updateInstance({ await db.managers.defguardInstances .filter((row) => row.id.equals(instance.id)) .update( - (_) => DefguardInstancesCompanion(poolingToken: drift.Value(token)), + (_) => + DefguardInstancesCompanion(poolingToken: drift.Value(token)), ); talker.debug("${instance.logName} token updated"); } @@ -124,7 +125,7 @@ String getInstanceUpdateMessage( UpdateInstanceResult updateResult, ) { final buffer = StringBuffer(); - if(updateResult.instanceChanged) { + if (updateResult.instanceChanged) { buffer.write("Instance information updated. "); } if (updateResult.locationsRemoved.isNotEmpty) { diff --git a/client/pubspec.yaml b/client/pubspec.yaml index ac44d0c0..cfbb766d 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.4+1 +version: 1.7.0+1 environment: sdk: ^3.8.1 From f2cc4dd6b88ecfbce319ee76f1d874ac5d2e7de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 08:11:15 +0200 Subject: [PATCH 27/65] Update Xcode project --- client/ios/Podfile.lock | 11 +-- client/ios/Runner.xcodeproj/project.pbxproj | 12 +++ client/pubspec.lock | 92 ++++++++++++--------- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index ff20001b..ce14a2c7 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -1,9 +1,6 @@ PODS: - app_links (6.4.1): - Flutter - - cupertino_http (0.0.1): - - Flutter - - FlutterMacOS - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) @@ -21,7 +18,7 @@ PODS: - FlutterMacOS - package_info_plus (0.4.5): - Flutter - - permission_handler_apple (9.3.0): + - permission_handler_apple (9.4.8): - Flutter - share_plus (0.0.1): - Flutter @@ -58,7 +55,6 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) @@ -81,8 +77,6 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" - cupertino_http: - :path: ".symlinks/plugins/cupertino_http/darwin" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: @@ -114,7 +108,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 - cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f @@ -123,7 +116,7 @@ SPEC CHECKSUMS: local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + permission_handler_apple: ee2fe0fd04551b304eb002714ff067c371c822ed share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 3309e0e5..d6e1d134 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -593,7 +593,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; @@ -608,6 +611,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -948,7 +952,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; @@ -963,6 +970,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -977,7 +985,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; @@ -992,6 +1003,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/client/pubspec.lock b/client/pubspec.lock index 4f57d029..9041cb56 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: code_assets - sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" code_builder: dependency: transitive description: @@ -277,18 +277,18 @@ packages: dependency: transitive description: name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.15.1" cronet_http: dependency: transitive description: name: cronet_http - sha256: "8e77bc6f203e0bc9126e6a9092508a3435dbcb04da3b53ed1a358909385c5e0e" + sha256: "9da9860b409d71e4b8259e3dee631176d499dee23e7cd45a3024ebd5181997d8" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" cross_file: dependency: transitive description: @@ -317,10 +317,10 @@ packages: dependency: transitive description: name: cupertino_http - sha256: "82cbec60c90bf785a047a9525688b6dacac444e177e1d5a5876963d3c50369e8" + sha256: "3c8c69cc1b94b9c7570d9454bf1e11fa7010b248547a0760843adc71f0c08fbe" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -373,10 +373,10 @@ packages: dependency: transitive description: name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.14" device_info_plus: dependency: "direct main" description: @@ -562,10 +562,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" url: "https://pub.dev" source: hosted - version: "2.0.34" + version: "2.0.35" flutter_riverpod: dependency: "direct main" description: @@ -716,10 +716,10 @@ packages: dependency: transitive description: name: hooks - sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.2" hooks_riverpod: dependency: "direct main" description: @@ -788,10 +788,10 @@ packages: dependency: transitive description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" io: dependency: transitive description: @@ -804,10 +804,18 @@ packages: dependency: transitive description: name: jni - sha256: "8706a77e94c76fe9ec9315e18949cc9479cc03af97085ca9c1077b61323ea12d" + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" url: "https://pub.dev" source: hosted - version: "0.15.2" + version: "1.0.1" js: dependency: transitive description: @@ -956,10 +964,18 @@ packages: dependency: "direct main" description: name: native_dio_adapter - sha256: "9bbfa5221fd287eb063962bbe6534290e5f87933e576fac210149fb80253b89a" + sha256: "89a84d8936a108c206e481b8da090422abb1351febac4956cd4a8113627ebb5e" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "0.19.1" node_preamble: dependency: transitive description: @@ -1020,42 +1036,42 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" url: "https://pub.dev" source: hosted - version: "2.2.23" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_windows: dependency: transitive description: @@ -1068,10 +1084,10 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 url: "https://pub.dev" source: hosted - version: "12.0.1" + version: "12.0.3" permission_handler_android: dependency: transitive description: @@ -1084,10 +1100,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.4.10" permission_handler_html: dependency: transitive description: @@ -1649,10 +1665,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e + sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.6" vector_math: dependency: transitive description: @@ -1765,5 +1781,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0 <4.0.0" - flutter: ">=3.38.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" From 9cc041188257b21680e082688f7441f4c67fdde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 08:23:42 +0200 Subject: [PATCH 28/65] Bump version to 1.7.0 in Xcode project --- client/ios/Runner.xcodeproj/project.pbxproj | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index d6e1d134..f994d6f5 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -609,6 +609,7 @@ "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", ); + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -648,7 +649,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.4; + MARKETING_VERSION = 1.7.0; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -702,7 +703,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.4; + MARKETING_VERSION = 1.7.0; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -753,7 +754,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.4; + MARKETING_VERSION = 1.7.0; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -968,6 +969,7 @@ "$(inherited)", "$(PROJECT_DIR)/boringtun/target/release", ); + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1001,6 +1003,7 @@ "$(inherited)", "$(PROJECT_DIR)/boringtun/target/release", ); + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 3c5f541f2fcfcb4f1c30c46dda78b8e84e6f24ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 08:27:29 +0200 Subject: [PATCH 29/65] Add verbosity --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9b261044..977ee13a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -52,7 +52,7 @@ jobs: run: pod repo update - name: Build iOS - run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} + run: flutter build ipa -v --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} - name: Get last commit message run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV From 53d6b8bbf1a41ee55716a19ea47cc43b2de8f1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 10:18:51 +0200 Subject: [PATCH 30/65] Auto sign VPNExtension --- client/ios/Runner.xcodeproj/project.pbxproj | 48 ++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index f994d6f5..539ded99 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -577,9 +577,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -600,6 +599,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -613,8 +613,12 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -629,8 +633,10 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtensionDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -638,7 +644,6 @@ INFOPLIST_FILE = VPNExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VPNExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -656,11 +661,12 @@ OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; @@ -683,8 +689,10 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -692,7 +700,6 @@ INFOPLIST_FILE = VPNExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VPNExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -709,11 +716,12 @@ OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; @@ -734,8 +742,10 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -743,7 +753,6 @@ INFOPLIST_FILE = VPNExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VPNExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -760,11 +769,12 @@ OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; @@ -882,10 +892,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -935,9 +944,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -960,6 +968,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -973,9 +982,13 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -994,6 +1007,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1007,8 +1021,12 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; From e3b86b04f991d7b5145cabcdcfcab61d80ac5b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 10:36:26 +0200 Subject: [PATCH 31/65] One entitlements --- client/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../VPNExtension/VPNExtension.entitlements | 4 ++++ .../VPNExtensionDebug.entitlements | 24 ------------------- 3 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 client/ios/VPNExtension/VPNExtensionDebug.entitlements diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 539ded99..943efa96 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -632,7 +632,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtensionDebug.entitlements; + CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/client/ios/VPNExtension/VPNExtension.entitlements b/client/ios/VPNExtension/VPNExtension.entitlements index 76663a00..8b31f34f 100644 --- a/client/ios/VPNExtension/VPNExtension.entitlements +++ b/client/ios/VPNExtension/VPNExtension.entitlements @@ -10,6 +10,10 @@ allow-vpn + com.apple.security.application-groups + + group.net.defguard.mobile + com.apple.security.app-sandbox com.apple.security.network.client diff --git a/client/ios/VPNExtension/VPNExtensionDebug.entitlements b/client/ios/VPNExtension/VPNExtensionDebug.entitlements deleted file mode 100644 index e4dc971a..00000000 --- a/client/ios/VPNExtension/VPNExtensionDebug.entitlements +++ /dev/null @@ -1,24 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.developer.networking.vpn.api - - allow-vpn - - com.apple.security.app-sandbox - - com.apple.security.application-groups - - group.net.defguard.mobile - - com.apple.security.network.client - - com.apple.security.network.server - - - From 0dd18f043c16981768a7e2819cbd95f13ecb7521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 10:57:51 +0200 Subject: [PATCH 32/65] pod reintegrate --- client/ios/Runner.xcodeproj/project.pbxproj | 96 ++++++++++----------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 943efa96..24c71e35 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 13AC128328D127CC1FFFF558 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B65B0851E892D176C6678C51 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 284986622E1FAAA700BBCE47 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 287C99B22E1D2FA400965674 /* NetworkExtension.framework */; }; 287C99BB2E1D2FA400965674 /* VPNExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 287C99B12E1D2FA400965674 /* VPNExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -17,8 +18,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - D44F9D7BC923612123D748C1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7D7C0FCE946457ECD05E164 /* Pods_RunnerTests.framework */; }; - FC45D341450122778AD42105 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CD43F789A53D9955D4328EF /* Pods_Runner.framework */; }; + DACB2DBAE80BDD7607EE4779 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -53,10 +53,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 086F73C348A8C9390DD1F1F3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 0CD8742B0F10B07586941587 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 201DFD21B12CFE608884216D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 2870EE742E20076800A83A9A /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2870EE782E2008B400A83A9A /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 287C99B12E1D2FA400965674 /* VPNExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VPNExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -65,16 +64,17 @@ 2886A14D2E28ED64006A7931 /* MockVPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVPNManager.swift; sourceTree = ""; }; 28DD48A42E3B5A7B008D3F6D /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 28F6C5EA2E1FD71200C01098 /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2E3317479FFAF5BBC897DD53 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 4760374363F2DA4B1CE23DB5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 6C3E6D10E91648BACCEB24E2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 6CD43F789A53D9955D4328EF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 424B254BB5DDF4D176DA0E7E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 5DA50AA8F82E69D4A7D35633 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 6E29205A5B56791C6A58AB8C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8DA37A462F22DF4069A30F0C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -82,8 +82,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E7D7C0FCE946457ECD05E164 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EE706D7345143032DF760E81 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B65B0851E892D176C6678C51 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BD3CFBCA806DBFB89630EE74 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -120,7 +120,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D44F9D7BC923612123D748C1 /* Pods_RunnerTests.framework in Frameworks */, + DACB2DBAE80BDD7607EE4779 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -128,7 +128,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FC45D341450122778AD42105 /* Pods_Runner.framework in Frameworks */, + 13AC128328D127CC1FFFF558 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -202,8 +202,8 @@ 2870EE742E20076800A83A9A /* wireguard_plugin.framework */, 28F6C5EA2E1FD71200C01098 /* wireguard_plugin.framework */, 287C99B22E1D2FA400965674 /* NetworkExtension.framework */, - 6CD43F789A53D9955D4328EF /* Pods_Runner.framework */, - E7D7C0FCE946457ECD05E164 /* Pods_RunnerTests.framework */, + B65B0851E892D176C6678C51 /* Pods_Runner.framework */, + 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -211,12 +211,12 @@ DE2536E81B1975928E1692FE /* Pods */ = { isa = PBXGroup; children = ( - 201DFD21B12CFE608884216D /* Pods-Runner.debug.xcconfig */, - 2E3317479FFAF5BBC897DD53 /* Pods-Runner.release.xcconfig */, - EE706D7345143032DF760E81 /* Pods-Runner.profile.xcconfig */, - 086F73C348A8C9390DD1F1F3 /* Pods-RunnerTests.debug.xcconfig */, - 6C3E6D10E91648BACCEB24E2 /* Pods-RunnerTests.release.xcconfig */, - 4760374363F2DA4B1CE23DB5 /* Pods-RunnerTests.profile.xcconfig */, + BD3CFBCA806DBFB89630EE74 /* Pods-Runner.debug.xcconfig */, + 6E29205A5B56791C6A58AB8C /* Pods-Runner.release.xcconfig */, + 5DA50AA8F82E69D4A7D35633 /* Pods-Runner.profile.xcconfig */, + 424B254BB5DDF4D176DA0E7E /* Pods-RunnerTests.debug.xcconfig */, + 8DA37A462F22DF4069A30F0C /* Pods-RunnerTests.release.xcconfig */, + 0CD8742B0F10B07586941587 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -248,7 +248,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 3CB5BDD8022927BDC0BBED95 /* [CP] Check Pods Manifest.lock */, + C42D5465F1F179E1EECF14FB /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 46C1966B05CC6B3688F00188 /* Frameworks */, @@ -267,15 +267,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 25F4A7C7AC197D56B91A98A9 /* [CP] Check Pods Manifest.lock */, + 12E55229B7D8012912191D7E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 287C99BC2E1D2FA400965674 /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - EF72AEB02D90D038422B97CE /* [CP] Embed Pods Frameworks */, - A2C99C1540BD0C64E164ABC6 /* [CP] Copy Pods Resources */, + BCC7D29F7E18EB3A195C9935 /* [CP] Embed Pods Frameworks */, + 7A28395B068B05A97BF2ECFE /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -360,7 +360,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 25F4A7C7AC197D56B91A98A9 /* [CP] Check Pods Manifest.lock */ = { + 12E55229B7D8012912191D7E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -398,26 +398,21 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; - 3CB5BDD8022927BDC0BBED95 /* [CP] Check Pods Manifest.lock */ = { + 7A28395B068B05A97BF2ECFE /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -435,38 +430,43 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - A2C99C1540BD0C64E164ABC6 /* [CP] Copy Pods Resources */ = { + BCC7D29F7E18EB3A195C9935 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - EF72AEB02D90D038422B97CE /* [CP] Embed Pods Frameworks */ = { + C42D5465F1F179E1EECF14FB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -787,7 +787,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 086F73C348A8C9390DD1F1F3 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 424B254BB5DDF4D176DA0E7E /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; @@ -807,7 +807,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6C3E6D10E91648BACCEB24E2 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 8DA37A462F22DF4069A30F0C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; @@ -825,7 +825,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4760374363F2DA4B1CE23DB5 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 0CD8742B0F10B07586941587 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; From f96b5819b7ddd5df9c59247658d5a46590370384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 11:12:16 +0200 Subject: [PATCH 33/65] CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO --- client/ios/Runner.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 24c71e35..7a84cbeb 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -563,6 +563,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 82GZ7KN29J; @@ -872,6 +873,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 82GZ7KN29J; @@ -930,6 +932,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 82GZ7KN29J; From aa5df25ecd46f593ce505c1aa684e9acd9d0db25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 11:53:39 +0200 Subject: [PATCH 34/65] One entitlements for Runner --- client/ios/Runner.xcodeproj/project.pbxproj | 10 ++++------ client/ios/Runner/RunnerDebug.entitlements | 22 --------------------- 2 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 client/ios/Runner/RunnerDebug.entitlements diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 7a84cbeb..39b0488f 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -62,7 +62,6 @@ 287C99B22E1D2FA400965674 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 287C9A272E1D43DB00965674 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 2886A14D2E28ED64006A7931 /* MockVPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVPNManager.swift; sourceTree = ""; }; - 28DD48A42E3B5A7B008D3F6D /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 28F6C5EA2E1FD71200C01098 /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; @@ -181,7 +180,6 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 28DD48A42E3B5A7B008D3F6D /* RunnerDebug.entitlements */, 287C9A272E1D43DB00965674 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -580,6 +578,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; + REGISTER_APP_GROUPS = YES; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -663,7 +662,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -718,7 +716,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -771,7 +768,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -897,6 +893,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + REGISTER_APP_GROUPS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -949,6 +946,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; + REGISTER_APP_GROUPS = YES; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -963,7 +961,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; diff --git a/client/ios/Runner/RunnerDebug.entitlements b/client/ios/Runner/RunnerDebug.entitlements deleted file mode 100644 index 1e76d2f7..00000000 --- a/client/ios/Runner/RunnerDebug.entitlements +++ /dev/null @@ -1,22 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.developer.networking.vpn.api - - allow-vpn - - com.apple.security.app-sandbox - - com.apple.security.application-groups - - group.net.defguard.mobile - - keychain-access-groups - - - From e54c175281ff8f8648fc831afa83e47b2fa60661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 12:01:26 +0200 Subject: [PATCH 35/65] Decrease verbosity --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 977ee13a..9b261044 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -52,7 +52,7 @@ jobs: run: pod repo update - name: Build iOS - run: flutter build ipa -v --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} + run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} - name: Get last commit message run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV From b710298621b6fe42608218f198660351dac3f979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:37:21 +0200 Subject: [PATCH 36/65] fix dev build fix platform info report to core (#195) --- .gitignore | 3 +++ client/android/app/build.gradle.kts | 5 +++++ client/android/app/proguard-rules.pro | 3 +++ client/lib/open/api.dart | 2 ++ 4 files changed, 13 insertions(+) create mode 100644 client/android/app/proguard-rules.pro diff --git a/.gitignore b/.gitignore index c5edab10..7b02be60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .envrc .direnv/ + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 4c434a44..760519ff 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -31,12 +31,17 @@ android { buildTypes { release { // let r0adkll/sign-android-release@v1 in CI do the signing + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + implementation("com.google.android.gms:play-services-cronet:18.1.1") implementation(files("../../../lib/tunnel.aar")) } diff --git a/client/android/app/proguard-rules.pro b/client/android/app/proguard-rules.pro new file mode 100644 index 00000000..1d11401a --- /dev/null +++ b/client/android/app/proguard-rules.pro @@ -0,0 +1,3 @@ +# ProGuard rules for cronet_http +-keep class io.flutter.plugins.cronet_http.** { *; } +-keep class org.chromium.net.** { *; } diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 2f46fbbe..60870d6a 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -71,6 +71,7 @@ class _ProxyApi { if (Platform.isAndroid) { final android = await deviceInfo.androidInfo; platformInfo = ClientPlatformInfo( + osFamily: 'android', osType: 'Android', version: android.version.release, codename: android.version.codename, @@ -80,6 +81,7 @@ class _ProxyApi { } else if (Platform.isIOS) { final ios = await deviceInfo.iosInfo; platformInfo = ClientPlatformInfo( + osFamily: 'ios', osType: 'iOS', version: ios.systemVersion, architecture: 'arm64', From 8eac50610bda411b9ae602e17bead305c346540e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:03:11 +0200 Subject: [PATCH 37/65] Proxy request error (#196) * human readable errors during oidc flow * formatting --- .../mfa/openid_mfa_waiting_screen.dart | 18 +++++-- .../lib/open/screens/mfa/mfa_code_screen.dart | 9 +++- client/lib/utils/error_handler.dart | 49 +++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 client/lib/utils/error_handler.dart diff --git a/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart b/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart index 6d120125..3970b966 100644 --- a/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart +++ b/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart @@ -13,6 +13,7 @@ import 'package:mobile/theme/text.dart'; import '../../../../../logging.dart'; import '../../../open/services/snackbar_service.dart'; +import '../../../utils/error_handler.dart'; class OpenIdMfaWaitingScreenData { final String proxyUrl; @@ -51,8 +52,18 @@ class OpenIdMfaWaitingScreen extends HookConsumerWidget { final response = await proxyApi.finishMfa(uri, request); return response; } on DioException catch (e) { - if (e.response?.statusCode == 428) { - talker.debug("User did not complete openid browser login, waiting"); + final isNetworkError = + e.type == DioExceptionType.connectionError || + e.type == DioExceptionType.connectionTimeout || + (e.error?.toString().contains("-1005") ?? false) || + (e.message?.contains("-1005") ?? false); + + if (e.response?.statusCode == 428 || isNetworkError) { + if (isNetworkError) { + talker.warning("Network error during MFA polling, retrying: $e"); + } else { + talker.debug("User did not complete openid browser login, waiting"); + } await Future.delayed(Duration(seconds: 2)); } else { rethrow; @@ -83,8 +94,9 @@ class OpenIdMfaWaitingScreen extends HookConsumerWidget { }) .catchError((error) { talker.error("OpenID MFA polling error: $error"); + final message = ErrorHandler.getHumanReadableError(error); SnackbarService.show( - "Error: $error", + message, textColor: DgColor.textAlert, dismissable: true, ); diff --git a/client/lib/open/screens/mfa/mfa_code_screen.dart b/client/lib/open/screens/mfa/mfa_code_screen.dart index 1a71e0f8..5a01c1a9 100644 --- a/client/lib/open/screens/mfa/mfa_code_screen.dart +++ b/client/lib/open/screens/mfa/mfa_code_screen.dart @@ -11,6 +11,7 @@ import 'package:mobile/open/widgets/navigation/dg_scaffold.dart'; import 'package:mobile/theme/color.dart'; import 'package:mobile/theme/spacing.dart'; import 'package:mobile/theme/text.dart'; +import 'package:mobile/utils/error_handler.dart'; import 'package:mobile/utils/screen_padding.dart'; import '../../../../../data/db/enums.dart'; @@ -151,9 +152,15 @@ class _CodeForm extends HookConsumerWidget { if (e.response?.statusCode == 401) { codeInvalid.value = true; formKey.currentState?.validate(); + } else { + SnackbarService.showError( + ErrorHandler.getHumanReadableError(e), + ); } } catch (e) { - SnackbarService.showError("Error: $e"); + SnackbarService.showError( + ErrorHandler.getHumanReadableError(e), + ); } finally { isLoading.value = false; } diff --git a/client/lib/utils/error_handler.dart b/client/lib/utils/error_handler.dart new file mode 100644 index 00000000..67387693 --- /dev/null +++ b/client/lib/utils/error_handler.dart @@ -0,0 +1,49 @@ +import 'package:dio/dio.dart'; + +class ErrorHandler { + static String getHumanReadableError(Object e) { + if (e is DioException) { + switch (e.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + return "Connection timed out. Please check your internet connection."; + case DioExceptionType.connectionError: + return "Unable to connect to the server. Please check your internet connection."; + case DioExceptionType.badResponse: + if (e.response?.statusCode == 401) { + return "Unauthorized. Please check your credentials."; + } + if (e.response?.statusCode == 403) { + return "Access forbidden."; + } + if (e.response?.statusCode == 404) { + return "Service not found."; + } + if (e.response?.statusCode != null && + e.response!.statusCode! >= 500) { + return "Server error. Please try again later."; + } + return "Server returned an error: ${e.response?.statusCode}"; + case DioExceptionType.cancel: + return "Request was cancelled."; + default: + // Handle specific iOS error -1005 (Network connection lost) + final errorString = e.error?.toString() ?? ""; + final messageString = e.message ?? ""; + if (errorString.contains("-1005") || + messageString.contains("-1005")) { + return "Network connection lost. Please try again."; + } + + return "An unexpected network error occurred."; + } + } + + final s = e.toString(); + if (s.startsWith("Exception: ")) { + return s.substring(11); + } + return s; + } +} From bebbfd8b729068dc34fc2e8937ffa9281c381cbd Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:16:19 +0200 Subject: [PATCH 38/65] chore: add Renovate config --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..75ec2adf --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>DefGuard/ci-workflows//renovate/default.json"], + "baseBranches": ["dev", "main"] +} From a47939e526e164c5e683182ba10ad8ba6876b90d Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 10 Jul 2026 11:43:28 +0200 Subject: [PATCH 39/65] iOS: fix split DNS (#240) --- .../VPNExtension/TunnelConfiguration.swift | 11 +++++++--- client/pubspec.lock | 20 +++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/client/ios/VPNExtension/TunnelConfiguration.swift b/client/ios/VPNExtension/TunnelConfiguration.swift index ca4f76e7..619a17ca 100644 --- a/client/ios/VPNExtension/TunnelConfiguration.swift +++ b/client/ios/VPNExtension/TunnelConfiguration.swift @@ -71,10 +71,15 @@ final class TunnelConfiguration: Codable { networkSettings.tunnelOverheadBytes = 80 let dnsSettings = NEDNSSettings(servers: dns) - dnsSettings.searchDomains = dnsSearch if !dns.isEmpty { - // Make all DNS queries go through the tunnel. - dnsSettings.matchDomains = [""] + if dnsSearch.isEmpty { + // Resolve all DNS queries. + dnsSettings.matchDomains = [""] + } else { + // Split DNS queries. + dnsSettings.matchDomains = dnsSearch + dnsSettings.searchDomains = dnsSearch + } } networkSettings.dnsSettings = dnsSettings diff --git a/client/pubspec.lock b/client/pubspec.lock index 9041cb56..65aceba7 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.2" build_resolvers: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" url: "https://pub.dev" source: hosted - version: "0.3.5+2" + version: "0.3.5+4" crypto: dependency: transitive description: @@ -397,10 +397,10 @@ packages: dependency: "direct main" description: name: dio - sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 url: "https://pub.dev" source: hosted - version: "5.9.2" + version: "5.10.0" dio_cookie_manager: dependency: "direct main" description: @@ -413,10 +413,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" drift: dependency: "direct main" description: @@ -972,10 +972,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 + sha256: f9c168717100ae6d9fee9ffb0be379bf1f8b26b0f6bcbd4fdddcd931993a6a72 url: "https://pub.dev" source: hosted - version: "0.19.1" + version: "0.19.2" node_preamble: dependency: transitive description: From 4f60fa15f47e6a0acb5c3e082b0623a60493bd2f Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 13 Jul 2026 11:22:44 +0200 Subject: [PATCH 40/65] New iOS build (#244) --- .github/workflows/build.yaml | 49 ++++++++++++++++++---------- .github/workflows/lint-and-test.yaml | 9 ++--- .github/workflows/sbom.yaml | 2 +- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9b261044..d2ce5038 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,13 +15,13 @@ on: jobs: build-ios: - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, native] defaults: run: working-directory: ./client steps: - name: Checkout main repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" @@ -40,7 +40,7 @@ jobs: run: flutter pub get - name: Unlock Keychain - run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain + run: security unlock-keychain -p "${{ secrets.BUILD_KEYCHAIN_PASSWORD }}" build.keychain - name: Create BoringTun directory run: mkdir -p ios/VPNExtension/BoringTun @@ -54,24 +54,37 @@ jobs: - name: Build iOS run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} - - name: Get last commit message - run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV - - name: Upload app to TestFlight - uses: apple-actions/upload-testflight-build@v5 # Mobile applications are published to the App Store manually, with release tags applied # post-publication. To avoid redundant uploads, this step executes only for non-tagged # builds, ensuring tagged releases are distributed exclusively to GitHub. - if: "!startsWith(github.ref, 'refs/tags/')" - with: - app-path: "client/build/ios/ipa/Defguard.ipa" - issuer-id: ${{ secrets.API_ISSUER_ID }} - api-key-id: ${{ secrets.ASC_API_KEY_ID }} - api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} - release-notes: ${{ env.COMMIT_MSG }} + run: | + xcrun altool --api-key ${{ secrets.ASC_API_KEY_ID }} \ + --api-issuer ${{ secrets.API_ISSUER_ID }} \ + --upload-app --platform ios --file build/ios/ipa/Defguard.ipa --wait + + - name: Upload What's New + env: + APP_ID: "6748068630" + run: | + UPLOAD_DIR=$(mktemp -d) + VERSION=$(grep '^version:' pubspec.yaml | cut -d ' ' -f 2 | cut -d '+' -f 1) + mkdir -p "${UPLOAD_DIR}/beta-${APP_ID}/upload/IOS" + git log -1 --pretty='"whatsNew" = "%B";' > "${UPLOAD_DIR}/beta-${APP_ID}/upload/IOS/en-GB.txt" + RETRIES=0 + until [ ${RETRIES} -gt 6 ] + do + xcrun altool --api-key ${{ secrets.ASC_API_KEY_ID }} --api-issuer ${{ secrets.API_ISSUER_ID }} \ + --apple-id ${APP_ID} --bundle-version ${{ github.run_number }} \--bundle-short-version-string ${VERSION} \ + --platform macos --beta-app-store-text "${UPLOAD_DIR}" --upload && break + echo "Waiting for app ${APP_ID} build ${{ github.run_number }} version ${VERSION}" + sleep 10 + ((RETRIES++)) + done + rm -f -r "${UPLOAD_DIR}" build-android: - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, native] env: ANDROID_HOME: /Users/admin/Library/Android/sdk ANDROID_SDK_ROOT: /Users/admin/Library/Android/sdk @@ -79,7 +92,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: submodules: "recursive" @@ -141,7 +154,7 @@ jobs: retention-days: 2 build-android-apk: - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, native] env: ANDROID_HOME: /Users/admin/Library/Android/sdk ANDROID_SDK_ROOT: /Users/admin/Library/Android/sdk @@ -149,7 +162,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 31bb7ce6..48a2de8f 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" @@ -57,7 +57,7 @@ jobs: # test-ios: # name: Run iOS tests - # runs-on: [self-hosted, macOS] + # runs-on: [self-hosted, macOS, native] # needs: lint # defaults: # run: @@ -65,7 +65,7 @@ jobs: # steps: # - name: Checkout - # uses: actions/checkout@v6 + # uses: actions/checkout@v7 # with: # submodules: "recursive" @@ -90,8 +90,5 @@ jobs: # # - name: build project # # run: flutter build ios - # - name: Unlock Keychain - # run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain - # - name: run plugin tests # run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index ec8c5cf3..425331a1 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -29,7 +29,7 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ steps.vars.outputs.TAG_NAME }} submodules: "recursive" From 9a33f004154f2715276e5cc389e8e4993c8c318c Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 20 Jul 2026 15:29:41 +0200 Subject: [PATCH 41/65] KeepAlive fix for iOS (#248) --- client/ios/VPNExtension/Adapter.swift | 4 ++-- client/ios/boringtun | 2 +- client/pubspec.lock | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ios/VPNExtension/Adapter.swift b/client/ios/VPNExtension/Adapter.swift index bd365003..103c1635 100644 --- a/client/ios/VPNExtension/Adapter.swift +++ b/client/ios/VPNExtension/Adapter.swift @@ -231,8 +231,8 @@ enum State { log.info("Creating keep-alive timer") let timer = DispatchSource.makeTimerSource(queue: ioQueue) timer.schedule( - deadline: .now() + .milliseconds(250), - repeating: .milliseconds(250), + deadline: .now() + .seconds(1), + repeating: .seconds(1), leeway: .milliseconds(25) ) timer.setEventHandler { [weak self] in diff --git a/client/ios/boringtun b/client/ios/boringtun index b7c29222..bb101128 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit b7c29222f9881165e514088cc8f6c6463e0aa452 +Subproject commit bb1011289f31ad7544f6dfb1fcaac74e608f7911 diff --git a/client/pubspec.lock b/client/pubspec.lock index 65aceba7..c022cc4c 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -956,10 +956,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce + sha256: ce3f059ebd6dbfab7292bba0e893e354b46730636820d3c9ef69005ce2d55bce url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.4.0" native_dio_adapter: dependency: "direct main" description: @@ -1641,10 +1641,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.6.0" vector_graphics: dependency: transitive description: From e18a3d888413b74e68611a50e4874222fe846612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 6 Aug 2026 09:35:00 +0200 Subject: [PATCH 42/65] upgrade major packages, upgrade flutter --- .gitignore | 9 +- client/lib/data/db/database.dart | 11 +- client/lib/data/db/database.g.dart | 24 -- client/lib/data/db/database_provider.dart | 11 + client/lib/data/db/database_provider.g.dart | 51 +++ .../lib/open/riverpod/biometrics_state.g.dart | 66 +++- .../riverpod/package_info/package_info.dart | 1 - .../riverpod/package_info/package_info.g.dart | 56 ++- client/lib/open/riverpod/plugin/plugin.g.dart | 68 +++- client/lib/open/riverpod/router/router.dart | 1 - client/lib/open/riverpod/router/router.g.dart | 58 ++- .../biometry/biometry_setup_screen.dart | 4 +- .../biometry/biometry_setup_screen.g.dart | 163 +++----- .../screens/instance/instance_screen.dart | 4 +- .../screens/instance/instance_screen.g.dart | 163 +++----- .../open/widgets/toaster/toast_manager.g.dart | 64 +++- client/lib/plugin.dart | 33 +- client/lib/router/routes.dart | 32 +- client/lib/router/routes.g.dart | 90 ++--- client/lib/utils/notifications.dart | 2 +- client/lib/utils/secure_storage.dart | 45 ++- client/pubspec.lock | 348 +++++++++--------- client/pubspec.yaml | 36 +- 23 files changed, 700 insertions(+), 640 deletions(-) create mode 100644 client/lib/data/db/database_provider.dart create mode 100644 client/lib/data/db/database_provider.g.dart diff --git a/.gitignore b/.gitignore index 7b02be60..c69c1188 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,11 @@ .direnv/ # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +# JetBrains IDEs +.idea/ +*.iml +*.iws +*.ipr +out/ diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index eb9c7aa2..6deb4135 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -1,10 +1,10 @@ import "package:drift/drift.dart"; import "package:drift_flutter/drift_flutter.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:mobile/data/db/database.steps.dart"; import "package:mobile/data/db/enums.dart"; import "package:path_provider/path_provider.dart"; -import "package:riverpod_annotation/riverpod_annotation.dart"; + +export 'database_provider.dart'; part 'database.g.dart'; @@ -154,12 +154,7 @@ class AppDatabase extends _$AppDatabase { } } -@Riverpod(keepAlive: true) -AppDatabase database(Ref ref) { - final db = AppDatabase(); - ref.onDispose(() => db.close()); - return db; -} +// database provider moved to database_provider.dart extension DefguardInstanceLogName on DefguardInstance { String get logName => '$name ($id)'; diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index 1638e6f8..3e30ea99 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -2781,27 +2781,3 @@ class $AppDatabaseManager { $$LocationsTableTableManager get locations => $$LocationsTableTableManager(_db, _db.locations); } - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$databaseHash() => r'd66464688f3f3beae31aa517238455b4413086f1'; - -/// See also [database]. -@ProviderFor(database) -final databaseProvider = Provider.internal( - database, - name: r'databaseProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$databaseHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef DatabaseRef = ProviderRef; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/client/lib/data/db/database_provider.dart b/client/lib/data/db/database_provider.dart new file mode 100644 index 00000000..33f90043 --- /dev/null +++ b/client/lib/data/db/database_provider.dart @@ -0,0 +1,11 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'database.dart'; + +part 'database_provider.g.dart'; + +@Riverpod(keepAlive: true) +AppDatabase database(Ref ref) { + final db = AppDatabase(); + ref.onDispose(() => db.close()); + return db; +} diff --git a/client/lib/data/db/database_provider.g.dart b/client/lib/data/db/database_provider.g.dart new file mode 100644 index 00000000..1156a1ef --- /dev/null +++ b/client/lib/data/db/database_provider.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(database) +final databaseProvider = DatabaseProvider._(); + +final class DatabaseProvider + extends $FunctionalProvider + with $Provider { + DatabaseProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'databaseProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$databaseHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + AppDatabase create(Ref ref) { + return database(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AppDatabase value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$databaseHash() => r'd66464688f3f3beae31aa517238455b4413086f1'; diff --git a/client/lib/open/riverpod/biometrics_state.g.dart b/client/lib/open/riverpod/biometrics_state.g.dart index 2cb46471..64e843e8 100644 --- a/client/lib/open/riverpod/biometrics_state.g.dart +++ b/client/lib/open/riverpod/biometrics_state.g.dart @@ -6,22 +6,58 @@ part of 'biometrics_state.dart'; // RiverpodGenerator // ************************************************************************** -String _$biometricsCapabilityHash() => - r'443b39915554d21ebbb63f65697aa62e8f6673d0'; +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [BiometricsCapability]. @ProviderFor(BiometricsCapability) -final biometricsCapabilityProvider = - NotifierProvider.internal( - BiometricsCapability.new, - name: r'biometricsCapabilityProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$biometricsCapabilityHash, - dependencies: null, - allTransitiveDependencies: null, +final biometricsCapabilityProvider = BiometricsCapabilityProvider._(); + +final class BiometricsCapabilityProvider + extends $NotifierProvider { + BiometricsCapabilityProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'biometricsCapabilityProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$biometricsCapabilityHash(); + + @$internal + @override + BiometricsCapability create() => BiometricsCapability(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(BiometricsState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), ); + } +} + +String _$biometricsCapabilityHash() => + r'443b39915554d21ebbb63f65697aa62e8f6673d0'; -typedef _$BiometricsCapability = Notifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +abstract class _$BiometricsCapability extends $Notifier { + BiometricsState build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + BiometricsState, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/client/lib/open/riverpod/package_info/package_info.dart b/client/lib/open/riverpod/package_info/package_info.dart index d46691ab..30e579bb 100644 --- a/client/lib/open/riverpod/package_info/package_info.dart +++ b/client/lib/open/riverpod/package_info/package_info.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/client/lib/open/riverpod/package_info/package_info.g.dart b/client/lib/open/riverpod/package_info/package_info.g.dart index b1d50da9..fc62f027 100644 --- a/client/lib/open/riverpod/package_info/package_info.g.dart +++ b/client/lib/open/riverpod/package_info/package_info.g.dart @@ -6,22 +6,44 @@ part of 'package_info.dart'; // RiverpodGenerator // ************************************************************************** -String _$packageInfoHash() => r'1345d87c48ac057095b818dcb92204d20a6e7dee'; +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [packageInfo]. @ProviderFor(packageInfo) -final packageInfoProvider = AutoDisposeFutureProvider.internal( - packageInfo, - name: r'packageInfoProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$packageInfoHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef PackageInfoRef = AutoDisposeFutureProviderRef; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +final packageInfoProvider = PackageInfoProvider._(); + +final class PackageInfoProvider + extends + $FunctionalProvider< + AsyncValue, + PackageInfo, + FutureOr + > + with $FutureModifier, $FutureProvider { + PackageInfoProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'packageInfoProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$packageInfoHash(); + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + return packageInfo(ref); + } +} + +String _$packageInfoHash() => r'1345d87c48ac057095b818dcb92204d20a6e7dee'; diff --git a/client/lib/open/riverpod/plugin/plugin.g.dart b/client/lib/open/riverpod/plugin/plugin.g.dart index 6fd22968..f1a445be 100644 --- a/client/lib/open/riverpod/plugin/plugin.g.dart +++ b/client/lib/open/riverpod/plugin/plugin.g.dart @@ -6,22 +6,60 @@ part of 'plugin.dart'; // RiverpodGenerator // ************************************************************************** -String _$pluginActiveTunnelStateHash() => - r'55599a4824466a2d18b3333ee242975b0de1c68b'; +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [PluginActiveTunnelState]. @ProviderFor(PluginActiveTunnelState) -final pluginActiveTunnelStateProvider = - NotifierProvider.internal( - PluginActiveTunnelState.new, - name: r'pluginActiveTunnelStateProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$pluginActiveTunnelStateHash, - dependencies: null, - allTransitiveDependencies: null, +final pluginActiveTunnelStateProvider = PluginActiveTunnelStateProvider._(); + +final class PluginActiveTunnelStateProvider + extends $NotifierProvider { + PluginActiveTunnelStateProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'pluginActiveTunnelStateProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$pluginActiveTunnelStateHash(); + + @$internal + @override + PluginActiveTunnelState create() => PluginActiveTunnelState(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PluginTunnelEventData? value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), ); + } +} + +String _$pluginActiveTunnelStateHash() => + r'55599a4824466a2d18b3333ee242975b0de1c68b'; -typedef _$PluginActiveTunnelState = Notifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +abstract class _$PluginActiveTunnelState + extends $Notifier { + PluginTunnelEventData? build(); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + PluginTunnelEventData?, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/client/lib/open/riverpod/router/router.dart b/client/lib/open/riverpod/router/router.dart index f5198edc..fc449238 100644 --- a/client/lib/open/riverpod/router/router.dart +++ b/client/lib/open/riverpod/router/router.dart @@ -1,4 +1,3 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile/router/routes.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/client/lib/open/riverpod/router/router.g.dart b/client/lib/open/riverpod/router/router.g.dart index fa34989a..72aefd72 100644 --- a/client/lib/open/riverpod/router/router.g.dart +++ b/client/lib/open/riverpod/router/router.g.dart @@ -6,22 +6,46 @@ part of 'router.dart'; // RiverpodGenerator // ************************************************************************** -String _$routerHash() => r'f5f080022520cf0ad41a6c2dd115809788b80104'; +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [router]. @ProviderFor(router) -final routerProvider = AutoDisposeProvider.internal( - router, - name: r'routerProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$routerHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef RouterRef = AutoDisposeProviderRef; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +final routerProvider = RouterProvider._(); + +final class RouterProvider + extends $FunctionalProvider + with $Provider { + RouterProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'routerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$routerHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + GoRouter create(Ref ref) { + return router(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(GoRouter value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$routerHash() => r'f5f080022520cf0ad41a6c2dd115809788b80104'; diff --git a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart index d24ae8cf..8c44d95e 100644 --- a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart +++ b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mobile/data/db/database.dart'; import 'package:mobile/open/riverpod/biometrics_state.dart'; import 'package:mobile/open/screens/add_instance/screens/biometry/widgets/biometry_setup_banner.dart'; import 'package:mobile/open/widgets/dg_single_child_scroll_view.dart'; @@ -10,7 +11,6 @@ import 'package:mobile/theme/spacing.dart'; import 'package:mobile/utils/screen_padding.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../../../../data/db/database.dart'; import '../../../../../logging.dart'; import '../../../../../router/routes.dart'; import '../../../../../theme/color.dart'; @@ -54,7 +54,7 @@ class BiometrySetupScreen extends StatelessWidget { } @riverpod -Stream _screenData(Ref ref, int id) { +Stream _screenData(Ref ref, int id) { final db = ref.read(databaseProvider); return db.managers.defguardInstances .filter((row) => row.id.equals(id)) diff --git a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.g.dart b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.g.dart index 8bf263e0..505e3d84 100644 --- a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.g.dart +++ b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.g.dart @@ -6,145 +6,74 @@ part of 'biometry_setup_screen.dart'; // RiverpodGenerator // ************************************************************************** -String _$screenDataHash() => r'7594254bb5290ce06be87b44f7d7e371181e505f'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [_screenData]. @ProviderFor(_screenData) -const _screenDataProvider = _ScreenDataFamily(); - -/// See also [_screenData]. -class _ScreenDataFamily extends Family> { - /// See also [_screenData]. - const _ScreenDataFamily(); - - /// See also [_screenData]. - _ScreenDataProvider call(int id) { - return _ScreenDataProvider(id); - } - - @override - _ScreenDataProvider getProviderOverride( - covariant _ScreenDataProvider provider, - ) { - return call(provider.id); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; +final _screenDataProvider = _ScreenDataFamily._(); + +final class _ScreenDataProvider + extends $FunctionalProvider, dynamic, Stream> + with $FutureModifier, $StreamProvider { + _ScreenDataProvider._({ + required _ScreenDataFamily super.from, + required int super.argument, + }) : super( + retry: null, + name: r'_screenDataProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; + String debugGetCreateSourceHash() => _$_screenDataHash(); @override - String? get name => r'_screenDataProvider'; -} - -/// See also [_screenData]. -class _ScreenDataProvider extends AutoDisposeStreamProvider { - /// See also [_screenData]. - _ScreenDataProvider(int id) - : this._internal( - (ref) => _screenData(ref as _ScreenDataRef, id), - from: _screenDataProvider, - name: r'_screenDataProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$screenDataHash, - dependencies: _ScreenDataFamily._dependencies, - allTransitiveDependencies: _ScreenDataFamily._allTransitiveDependencies, - id: id, - ); - - _ScreenDataProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.id, - }) : super.internal(); - - final int id; + String toString() { + return r'_screenDataProvider' + '' + '($argument)'; + } + @$internal @override - Override overrideWith( - Stream Function(_ScreenDataRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: _ScreenDataProvider._internal( - (ref) => create(ref as _ScreenDataRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - id: id, - ), - ); - } + $StreamProviderElement $createElement($ProviderPointer pointer) => + $StreamProviderElement(pointer); @override - AutoDisposeStreamProviderElement createElement() { - return _ScreenDataProviderElement(this); + Stream create(Ref ref) { + final argument = this.argument as int; + return _screenData(ref, argument); } @override bool operator ==(Object other) { - return other is _ScreenDataProvider && other.id == id; + return other is _ScreenDataProvider && other.argument == argument; } @override int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); + return argument.hashCode; } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin _ScreenDataRef on AutoDisposeStreamProviderRef { - /// The parameter `id` of this provider. - int get id; -} +String _$_screenDataHash() => r'814d13abd5143e8758c3b4d16bde3a3aa3af19bf'; + +final class _ScreenDataFamily extends $Family + with $FunctionalFamilyOverride, int> { + _ScreenDataFamily._() + : super( + retry: null, + name: r'_screenDataProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); -class _ScreenDataProviderElement - extends AutoDisposeStreamProviderElement - with _ScreenDataRef { - _ScreenDataProviderElement(super.provider); + _ScreenDataProvider call(int id) => + _ScreenDataProvider._(argument: id, from: this); @override - int get id => (origin as _ScreenDataProvider).id; + String toString() => r'_screenDataProvider'; } - -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart index 310acefd..05e15b73 100644 --- a/client/lib/open/screens/instance/instance_screen.dart +++ b/client/lib/open/screens/instance/instance_screen.dart @@ -8,9 +8,9 @@ import 'package:mobile/data/plugin/plugin.dart'; import 'package:mobile/open/api.dart'; import 'package:mobile/open/riverpod/biometrics_state.dart'; import 'package:mobile/open/riverpod/plugin/plugin.dart'; +import 'package:mobile/open/screens/instance/services/tunnel_service.dart'; import 'package:mobile/open/screens/instance/widgets/connection_conflict_dialog.dart'; import 'package:mobile/open/screens/instance/widgets/delete_instance_dialog.dart'; -import 'package:mobile/open/screens/instance/services/tunnel_service.dart'; import 'package:mobile/open/screens/instance/widgets/mfa_method_dialog.dart'; import 'package:mobile/open/screens/instance/widgets/refresh_instance_dialog.dart'; import 'package:mobile/open/screens/instance/widgets/routing_method_dialog.dart'; @@ -53,7 +53,7 @@ class _ScreenData { } @riverpod -Stream<_ScreenData?> _screenData(Ref ref, String id) { +Stream _screenData(Ref ref, String id) { final db = ref.read(databaseProvider); final parsedId = int.parse(id); final query = db.select(db.defguardInstances).join([ diff --git a/client/lib/open/screens/instance/instance_screen.g.dart b/client/lib/open/screens/instance/instance_screen.g.dart index d8fe686b..9b31ee47 100644 --- a/client/lib/open/screens/instance/instance_screen.g.dart +++ b/client/lib/open/screens/instance/instance_screen.g.dart @@ -6,145 +6,74 @@ part of 'instance_screen.dart'; // RiverpodGenerator // ************************************************************************** -String _$screenDataHash() => r'016a0d30ae8ccf1c897eaa360df857cc9ab8dd4e'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [_screenData]. @ProviderFor(_screenData) -const _screenDataProvider = _ScreenDataFamily(); - -/// See also [_screenData]. -class _ScreenDataFamily extends Family> { - /// See also [_screenData]. - const _ScreenDataFamily(); - - /// See also [_screenData]. - _ScreenDataProvider call(String id) { - return _ScreenDataProvider(id); - } - - @override - _ScreenDataProvider getProviderOverride( - covariant _ScreenDataProvider provider, - ) { - return call(provider.id); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; +final _screenDataProvider = _ScreenDataFamily._(); + +final class _ScreenDataProvider + extends $FunctionalProvider, dynamic, Stream> + with $FutureModifier, $StreamProvider { + _ScreenDataProvider._({ + required _ScreenDataFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'_screenDataProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; + String debugGetCreateSourceHash() => _$_screenDataHash(); @override - String? get name => r'_screenDataProvider'; -} - -/// See also [_screenData]. -class _ScreenDataProvider extends AutoDisposeStreamProvider<_ScreenData?> { - /// See also [_screenData]. - _ScreenDataProvider(String id) - : this._internal( - (ref) => _screenData(ref as _ScreenDataRef, id), - from: _screenDataProvider, - name: r'_screenDataProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$screenDataHash, - dependencies: _ScreenDataFamily._dependencies, - allTransitiveDependencies: _ScreenDataFamily._allTransitiveDependencies, - id: id, - ); - - _ScreenDataProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.id, - }) : super.internal(); - - final String id; + String toString() { + return r'_screenDataProvider' + '' + '($argument)'; + } + @$internal @override - Override overrideWith( - Stream<_ScreenData?> Function(_ScreenDataRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: _ScreenDataProvider._internal( - (ref) => create(ref as _ScreenDataRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - id: id, - ), - ); - } + $StreamProviderElement $createElement($ProviderPointer pointer) => + $StreamProviderElement(pointer); @override - AutoDisposeStreamProviderElement<_ScreenData?> createElement() { - return _ScreenDataProviderElement(this); + Stream create(Ref ref) { + final argument = this.argument as String; + return _screenData(ref, argument); } @override bool operator ==(Object other) { - return other is _ScreenDataProvider && other.id == id; + return other is _ScreenDataProvider && other.argument == argument; } @override int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, id.hashCode); - - return _SystemHash.finish(hash); + return argument.hashCode; } } -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin _ScreenDataRef on AutoDisposeStreamProviderRef<_ScreenData?> { - /// The parameter `id` of this provider. - String get id; -} +String _$_screenDataHash() => r'79d7705496a191283ceebc7ee61560a7bdbc2fe4'; + +final class _ScreenDataFamily extends $Family + with $FunctionalFamilyOverride, String> { + _ScreenDataFamily._() + : super( + retry: null, + name: r'_screenDataProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); -class _ScreenDataProviderElement - extends AutoDisposeStreamProviderElement<_ScreenData?> - with _ScreenDataRef { - _ScreenDataProviderElement(super.provider); + _ScreenDataProvider call(String id) => + _ScreenDataProvider._(argument: id, from: this); @override - String get id => (origin as _ScreenDataProvider).id; + String toString() => r'_screenDataProvider'; } - -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/client/lib/open/widgets/toaster/toast_manager.g.dart b/client/lib/open/widgets/toaster/toast_manager.g.dart index f65960f2..98aaaf55 100644 --- a/client/lib/open/widgets/toaster/toast_manager.g.dart +++ b/client/lib/open/widgets/toaster/toast_manager.g.dart @@ -6,21 +6,57 @@ part of 'toast_manager.dart'; // RiverpodGenerator // ************************************************************************** -String _$toastManagerHash() => r'74289bbdb571bc6704c109ff8277a455d0815c55'; +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning -/// See also [ToastManager]. @ProviderFor(ToastManager) -final toastManagerProvider = - NotifierProvider>.internal( - ToastManager.new, - name: r'toastManagerProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$toastManagerHash, - dependencies: null, - allTransitiveDependencies: null, +final toastManagerProvider = ToastManagerProvider._(); + +final class ToastManagerProvider + extends $NotifierProvider> { + ToastManagerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'toastManagerProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$toastManagerHash(); + + @$internal + @override + ToastManager create() => ToastManager(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(List value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>(value), ); + } +} + +String _$toastManagerHash() => r'74289bbdb571bc6704c109ff8277a455d0815c55'; -typedef _$ToastManager = Notifier>; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package +abstract class _$ToastManager extends $Notifier> { + List build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, List>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, List>, + List, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/client/lib/plugin.dart b/client/lib/plugin.dart index 4c282b6b..6a8686aa 100644 --- a/client/lib/plugin.dart +++ b/client/lib/plugin.dart @@ -1,9 +1,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile/data/plugin/plugin.dart'; -import 'package:mobile/utils/notifications.dart'; import 'package:mobile/open/riverpod/plugin/plugin.dart'; import 'package:mobile/open/widgets/toaster/toast_manager.dart'; +import 'package:mobile/utils/notifications.dart'; import 'package:wireguard_plugin/wireguard_plugin.dart'; import 'logging.dart'; @@ -13,18 +13,14 @@ final wireguardPluginProvider = Provider((ref) { return plugin; }); -class PluginEventRouter extends StateNotifier { - final Ref ref; - - PluginEventRouter(this.ref) : super(null) { +class PluginEventRouter extends Notifier { + @override + void build() { final plugin = ref.read(wireguardPluginProvider); plugin.startListening(onEvent: handleEvent); - } - - @override - void dispose() { - ref.read(wireguardPluginProvider).stopListening(); - super.dispose(); + ref.onDispose(() { + ref.read(wireguardPluginProvider).stopListening(); + }); } void handleEvent(String event, Map? data) { @@ -67,10 +63,10 @@ class PluginEventRouter extends StateNotifier { void notifyMfaSessionExpired() { // show system notification flutterLocalNotificationsPlugin.show( - 0, - 'Connection Lost', - 'VPN gateway unreachable, MFA session expired. Reconnect to continue.', - const NotificationDetails( + id: 0, + title: 'Connection Lost', + body: 'VPN gateway unreachable, MFA session expired. Reconnect to continue.', + notificationDetails: const NotificationDetails( android: AndroidNotificationDetails( 'defguard_channel', 'DefGuard', @@ -92,7 +88,6 @@ class PluginEventRouter extends StateNotifier { } } -final pluginEventRouterProvider = - StateNotifierProvider( - (ref) => PluginEventRouter(ref), - ); +final pluginEventRouterProvider = NotifierProvider( + PluginEventRouter.new, +); diff --git a/client/lib/router/routes.dart b/client/lib/router/routes.dart index c7c4f228..ca7ddcc4 100644 --- a/client/lib/router/routes.dart +++ b/client/lib/router/routes.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:mobile/enterprise/screens/mfa/openid_mfa_screen.dart'; +import 'package:mobile/enterprise/screens/mfa/openid_mfa_waiting_screen.dart'; import 'package:mobile/open/screens/add_instance/add_instance_screen.dart'; import 'package:mobile/open/screens/add_instance/screens/add_instance_form.dart'; import 'package:mobile/open/screens/add_instance/screens/biometry/biometry_finish_screen.dart'; @@ -9,8 +11,6 @@ import 'package:mobile/open/screens/add_instance/screens/name_device_screen.dart import 'package:mobile/open/screens/home/home_screen.dart'; import 'package:mobile/open/screens/instance/instance_screen.dart'; import 'package:mobile/open/screens/mfa/mfa_code_screen.dart'; -import 'package:mobile/enterprise/screens/mfa/openid_mfa_screen.dart'; -import 'package:mobile/enterprise/screens/mfa/openid_mfa_waiting_screen.dart'; import 'package:mobile/open/screens/process_qr_screen.dart'; import 'package:mobile/open/screens/scan_qr_screen.dart'; import 'package:talker_flutter/talker_flutter.dart'; @@ -21,7 +21,7 @@ part 'routes.g.dart'; @TypedGoRoute(path: "/process_qr") @immutable -class ProcessQrScreenRoute extends GoRouteData with _$ProcessQrScreenRoute { +class ProcessQrScreenRoute extends GoRouteData with $ProcessQrScreenRoute { const ProcessQrScreenRoute(this.$extra); final ProcessQrScreenData $extra; @@ -34,7 +34,7 @@ class ProcessQrScreenRoute extends GoRouteData with _$ProcessQrScreenRoute { @TypedGoRoute(path: '/') @immutable -class HomeScreenRoute extends GoRouteData with _$HomeScreenRoute { +class HomeScreenRoute extends GoRouteData with $HomeScreenRoute { const HomeScreenRoute(); @override @@ -45,7 +45,7 @@ class HomeScreenRoute extends GoRouteData with _$HomeScreenRoute { @TypedGoRoute(path: "/qr") @immutable -class QRScreenRoute extends GoRouteData with _$QRScreenRoute { +class QRScreenRoute extends GoRouteData with $QRScreenRoute { const QRScreenRoute(this.$extra); final QrScreenData $extra; @@ -58,7 +58,7 @@ class QRScreenRoute extends GoRouteData with _$QRScreenRoute { @TypedGoRoute(path: "/instance/:id") @immutable -class InstanceScreenRoute extends GoRouteData with _$InstanceScreenRoute { +class InstanceScreenRoute extends GoRouteData with $InstanceScreenRoute { final String id; const InstanceScreenRoute({required this.id}); @@ -71,7 +71,7 @@ class InstanceScreenRoute extends GoRouteData with _$InstanceScreenRoute { @TypedGoRoute(path: "/add_instance/name_device") @immutable -class NameDeviceScreenRoute extends GoRouteData with _$NameDeviceScreenRoute { +class NameDeviceScreenRoute extends GoRouteData with $NameDeviceScreenRoute { const NameDeviceScreenRoute(this.$extra); final NameDeviceScreenData $extra; @@ -85,7 +85,7 @@ class NameDeviceScreenRoute extends GoRouteData with _$NameDeviceScreenRoute { @TypedGoRoute(path: "/add_instance/form") @immutable class AddInstanceFormScreenRoute extends GoRouteData - with _$AddInstanceFormScreenRoute { + with $AddInstanceFormScreenRoute { @override Widget build(BuildContext context, GoRouterState state) { return AddInstanceFormScreen(); @@ -94,7 +94,7 @@ class AddInstanceFormScreenRoute extends GoRouteData @TypedGoRoute(path: '/add_instance/init') @immutable -class AddInstanceScreenRoute extends GoRouteData with _$AddInstanceScreenRoute { +class AddInstanceScreenRoute extends GoRouteData with $AddInstanceScreenRoute { const AddInstanceScreenRoute(); @override @@ -105,7 +105,7 @@ class AddInstanceScreenRoute extends GoRouteData with _$AddInstanceScreenRoute { @TypedGoRoute(path: "/talker") @immutable -class TalkerScreenRoute extends GoRouteData with _$TalkerScreenRoute { +class TalkerScreenRoute extends GoRouteData with $TalkerScreenRoute { @override Widget build(BuildContext context, GoRouterState state) { return TalkerScreen(talker: talker); @@ -114,7 +114,7 @@ class TalkerScreenRoute extends GoRouteData with _$TalkerScreenRoute { @TypedGoRoute(path: "/mfa/openid") @immutable -class OpenIdMfaScreenRoute extends GoRouteData with _$OpenIdMfaScreenRoute { +class OpenIdMfaScreenRoute extends GoRouteData with $OpenIdMfaScreenRoute { const OpenIdMfaScreenRoute(this.$extra); final OpenIdMfaScreenData $extra; @@ -128,7 +128,7 @@ class OpenIdMfaScreenRoute extends GoRouteData with _$OpenIdMfaScreenRoute { @TypedGoRoute(path: "/mfa/openid/waiting") @immutable class OpenIdMfaWaitingScreenRoute extends GoRouteData - with _$OpenIdMfaWaitingScreenRoute { + with $OpenIdMfaWaitingScreenRoute { const OpenIdMfaWaitingScreenRoute(this.$extra); final OpenIdMfaWaitingScreenData $extra; @@ -141,7 +141,7 @@ class OpenIdMfaWaitingScreenRoute extends GoRouteData @TypedGoRoute(path: "/mfa/code") @immutable -class MfaCodeScreenRoute extends GoRouteData with _$MfaCodeScreenRoute { +class MfaCodeScreenRoute extends GoRouteData with $MfaCodeScreenRoute { const MfaCodeScreenRoute(this.$extra); final MfaCodeScreenData $extra; @@ -155,7 +155,7 @@ class MfaCodeScreenRoute extends GoRouteData with _$MfaCodeScreenRoute { @TypedGoRoute(path: "/biometry_setup/:id") @immutable class BiometrySetupScreenRoute extends GoRouteData - with _$BiometrySetupScreenRoute { + with $BiometrySetupScreenRoute { final String id; const BiometrySetupScreenRoute({required this.id}); @@ -169,7 +169,7 @@ class BiometrySetupScreenRoute extends GoRouteData @TypedGoRoute(path: "/biometry_failed") @immutable class BiometrySetupFailedScreenRoute extends GoRouteData - with _$BiometrySetupFailedScreenRoute { + with $BiometrySetupFailedScreenRoute { const BiometrySetupFailedScreenRoute(); @override @@ -181,7 +181,7 @@ class BiometrySetupFailedScreenRoute extends GoRouteData @TypedGoRoute(path: "/biometry_finish") @immutable class BiometryFinishScreenRoute extends GoRouteData - with _$BiometryFinishScreenRoute { + with $BiometryFinishScreenRoute { const BiometryFinishScreenRoute(); @override diff --git a/client/lib/router/routes.g.dart b/client/lib/router/routes.g.dart index 2a813483..1b3fac66 100644 --- a/client/lib/router/routes.g.dart +++ b/client/lib/router/routes.g.dart @@ -25,11 +25,11 @@ List get $appRoutes => [ RouteBase get $processQrScreenRoute => GoRouteData.$route( path: '/process_qr', - - factory: _$ProcessQrScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $ProcessQrScreenRoute._fromState, ); -mixin _$ProcessQrScreenRoute on GoRouteData { +mixin $ProcessQrScreenRoute on GoRouteData { static ProcessQrScreenRoute _fromState(GoRouterState state) => ProcessQrScreenRoute(state.extra as ProcessQrScreenData); @@ -54,10 +54,13 @@ mixin _$ProcessQrScreenRoute on GoRouteData { context.replace(location, extra: _self.$extra); } -RouteBase get $homeScreenRoute => - GoRouteData.$route(path: '/', factory: _$HomeScreenRoute._fromState); +RouteBase get $homeScreenRoute => GoRouteData.$route( + path: '/', + hasOverriddenOnExit: false, + factory: $HomeScreenRoute._fromState, +); -mixin _$HomeScreenRoute on GoRouteData { +mixin $HomeScreenRoute on GoRouteData { static HomeScreenRoute _fromState(GoRouterState state) => const HomeScreenRoute(); @@ -78,10 +81,13 @@ mixin _$HomeScreenRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $qRScreenRoute => - GoRouteData.$route(path: '/qr', factory: _$QRScreenRoute._fromState); +RouteBase get $qRScreenRoute => GoRouteData.$route( + path: '/qr', + hasOverriddenOnExit: false, + factory: $QRScreenRoute._fromState, +); -mixin _$QRScreenRoute on GoRouteData { +mixin $QRScreenRoute on GoRouteData { static QRScreenRoute _fromState(GoRouterState state) => QRScreenRoute(state.extra as QrScreenData); @@ -108,11 +114,11 @@ mixin _$QRScreenRoute on GoRouteData { RouteBase get $instanceScreenRoute => GoRouteData.$route( path: '/instance/:id', - - factory: _$InstanceScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $InstanceScreenRoute._fromState, ); -mixin _$InstanceScreenRoute on GoRouteData { +mixin $InstanceScreenRoute on GoRouteData { static InstanceScreenRoute _fromState(GoRouterState state) => InstanceScreenRoute(id: state.pathParameters['id']!); @@ -138,11 +144,11 @@ mixin _$InstanceScreenRoute on GoRouteData { RouteBase get $nameDeviceScreenRoute => GoRouteData.$route( path: '/add_instance/name_device', - - factory: _$NameDeviceScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $NameDeviceScreenRoute._fromState, ); -mixin _$NameDeviceScreenRoute on GoRouteData { +mixin $NameDeviceScreenRoute on GoRouteData { static NameDeviceScreenRoute _fromState(GoRouterState state) => NameDeviceScreenRoute(state.extra as NameDeviceScreenData); @@ -169,11 +175,11 @@ mixin _$NameDeviceScreenRoute on GoRouteData { RouteBase get $addInstanceFormScreenRoute => GoRouteData.$route( path: '/add_instance/form', - - factory: _$AddInstanceFormScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $AddInstanceFormScreenRoute._fromState, ); -mixin _$AddInstanceFormScreenRoute on GoRouteData { +mixin $AddInstanceFormScreenRoute on GoRouteData { static AddInstanceFormScreenRoute _fromState(GoRouterState state) => AddInstanceFormScreenRoute(); @@ -196,11 +202,11 @@ mixin _$AddInstanceFormScreenRoute on GoRouteData { RouteBase get $addInstanceScreenRoute => GoRouteData.$route( path: '/add_instance/init', - - factory: _$AddInstanceScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $AddInstanceScreenRoute._fromState, ); -mixin _$AddInstanceScreenRoute on GoRouteData { +mixin $AddInstanceScreenRoute on GoRouteData { static AddInstanceScreenRoute _fromState(GoRouterState state) => const AddInstanceScreenRoute(); @@ -223,11 +229,11 @@ mixin _$AddInstanceScreenRoute on GoRouteData { RouteBase get $talkerScreenRoute => GoRouteData.$route( path: '/talker', - - factory: _$TalkerScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $TalkerScreenRoute._fromState, ); -mixin _$TalkerScreenRoute on GoRouteData { +mixin $TalkerScreenRoute on GoRouteData { static TalkerScreenRoute _fromState(GoRouterState state) => TalkerScreenRoute(); @@ -250,11 +256,11 @@ mixin _$TalkerScreenRoute on GoRouteData { RouteBase get $openIdMfaScreenRoute => GoRouteData.$route( path: '/mfa/openid', - - factory: _$OpenIdMfaScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $OpenIdMfaScreenRoute._fromState, ); -mixin _$OpenIdMfaScreenRoute on GoRouteData { +mixin $OpenIdMfaScreenRoute on GoRouteData { static OpenIdMfaScreenRoute _fromState(GoRouterState state) => OpenIdMfaScreenRoute(state.extra as OpenIdMfaScreenData); @@ -281,11 +287,11 @@ mixin _$OpenIdMfaScreenRoute on GoRouteData { RouteBase get $openIdMfaWaitingScreenRoute => GoRouteData.$route( path: '/mfa/openid/waiting', - - factory: _$OpenIdMfaWaitingScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $OpenIdMfaWaitingScreenRoute._fromState, ); -mixin _$OpenIdMfaWaitingScreenRoute on GoRouteData { +mixin $OpenIdMfaWaitingScreenRoute on GoRouteData { static OpenIdMfaWaitingScreenRoute _fromState(GoRouterState state) => OpenIdMfaWaitingScreenRoute(state.extra as OpenIdMfaWaitingScreenData); @@ -312,11 +318,11 @@ mixin _$OpenIdMfaWaitingScreenRoute on GoRouteData { RouteBase get $mfaCodeScreenRoute => GoRouteData.$route( path: '/mfa/code', - - factory: _$MfaCodeScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $MfaCodeScreenRoute._fromState, ); -mixin _$MfaCodeScreenRoute on GoRouteData { +mixin $MfaCodeScreenRoute on GoRouteData { static MfaCodeScreenRoute _fromState(GoRouterState state) => MfaCodeScreenRoute(state.extra as MfaCodeScreenData); @@ -343,11 +349,11 @@ mixin _$MfaCodeScreenRoute on GoRouteData { RouteBase get $biometrySetupScreenRoute => GoRouteData.$route( path: '/biometry_setup/:id', - - factory: _$BiometrySetupScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $BiometrySetupScreenRoute._fromState, ); -mixin _$BiometrySetupScreenRoute on GoRouteData { +mixin $BiometrySetupScreenRoute on GoRouteData { static BiometrySetupScreenRoute _fromState(GoRouterState state) => BiometrySetupScreenRoute(id: state.pathParameters['id']!); @@ -373,11 +379,11 @@ mixin _$BiometrySetupScreenRoute on GoRouteData { RouteBase get $biometrySetupFailedScreenRoute => GoRouteData.$route( path: '/biometry_failed', - - factory: _$BiometrySetupFailedScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $BiometrySetupFailedScreenRoute._fromState, ); -mixin _$BiometrySetupFailedScreenRoute on GoRouteData { +mixin $BiometrySetupFailedScreenRoute on GoRouteData { static BiometrySetupFailedScreenRoute _fromState(GoRouterState state) => const BiometrySetupFailedScreenRoute(); @@ -400,11 +406,11 @@ mixin _$BiometrySetupFailedScreenRoute on GoRouteData { RouteBase get $biometryFinishScreenRoute => GoRouteData.$route( path: '/biometry_finish', - - factory: _$BiometryFinishScreenRoute._fromState, + hasOverriddenOnExit: false, + factory: $BiometryFinishScreenRoute._fromState, ); -mixin _$BiometryFinishScreenRoute on GoRouteData { +mixin $BiometryFinishScreenRoute on GoRouteData { static BiometryFinishScreenRoute _fromState(GoRouterState state) => const BiometryFinishScreenRoute(); diff --git a/client/lib/utils/notifications.dart b/client/lib/utils/notifications.dart index 855ddc60..714ad26e 100644 --- a/client/lib/utils/notifications.dart +++ b/client/lib/utils/notifications.dart @@ -18,7 +18,7 @@ Future initNotifications() async { macOS: initializationSettingsDarwin, ); - await flutterLocalNotificationsPlugin.initialize(initializationSettings); + await flutterLocalNotificationsPlugin.initialize(settings: initializationSettings); } Future requestNotificationPermissions() async { diff --git a/client/lib/utils/secure_storage.dart b/client/lib/utils/secure_storage.dart index 38ea5331..cd9780a6 100644 --- a/client/lib/utils/secure_storage.dart +++ b/client/lib/utils/secure_storage.dart @@ -5,33 +5,38 @@ import 'package:flutter/services.dart'; import 'package:mobile/data/proxy/mfa.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:local_auth/local_auth.dart'; -import 'package:local_auth/error_codes.dart' as auth_error; import 'package:mobile/logging.dart'; class UserCanceledAuth implements Exception { const UserCanceledAuth(); } -String getErrorMessageFromBiometricsException(PlatformException e) { - final errorCode = e.code; - talker.error("Biometrics check failed with code: $errorCode"); - if (errorCode == auth_error.notAvailable) { - return "Device is not supported or is busy."; - } - if (errorCode == auth_error.biometricOnlyNotSupported) { - return "Biometrics auth is not configured on the device."; - } - if (errorCode == auth_error.lockedOut) { - return "Biometrics auth is not configured on the device."; +String getErrorMessageFromBiometricsException(Object e) { + if (e is LocalAuthException) { + talker.error("Biometrics check failed with code: ${e.code}"); + switch (e.code) { + case LocalAuthExceptionCode.noBiometricHardware: + return "Device is not supported or is busy."; + case LocalAuthExceptionCode.noBiometricsEnrolled: + case LocalAuthExceptionCode.noCredentialsSet: + return "Biometrics auth is not configured on the device."; + case LocalAuthExceptionCode.biometricLockout: + case LocalAuthExceptionCode.temporaryLockout: + return "Biometrics auth is temporarily locked."; + default: + return "Unknown error: ${e.description ?? 'No description'}"; + } } - if (errorCode == auth_error.notEnrolled) { - return "Biometrics auth is not configured on the device."; + if (e is PlatformException) { + talker.error("Biometrics check failed with PlatformException code: ${e.code}"); + return "Platform error: ${e.message ?? 'Unknown error'}"; } + talker.error("Biometrics check failed with unexpected error: $e"); return "Unknown error"; } AndroidOptions _getAndroidOptions() => - const AndroidOptions(encryptedSharedPreferences: true); + const AndroidOptions(); FlutterSecureStorage _getStorage() => FlutterSecureStorage(aOptions: _getAndroidOptions()); @@ -62,10 +67,7 @@ Future createBiometricStorage( final auth = LocalAuthentication(); if (await auth.authenticate( localizedReason: prompt ?? "Authenticate to proceed", - options: const AuthenticationOptions( - useErrorDialogs: false, - biometricOnly: true, - ), + biometricOnly: true, )) { final storage = _getStorage(); final instanceStorage = _generateInstanceStorage(); @@ -84,10 +86,7 @@ Future getBiometricInstanceStorage( final auth = LocalAuthentication(); if (await auth.authenticate( localizedReason: message, - options: const AuthenticationOptions( - useErrorDialogs: false, - biometricOnly: true, - ), + biometricOnly: true, )) { final storage = _getStorage(); final storeRawData = await storage.read(key: storageKey); diff --git a/client/pubspec.lock b/client/pubspec.lock index c022cc4c..0ca3b117 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d url: "https://pub.dev" source: hosted - version: "85.0.0" + version: "91.0.0" adaptive_number: dependency: transitive description: @@ -17,22 +17,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + analysis_server_plugin: + dependency: transitive + description: + name: analysis_server_plugin + sha256: "26844e7f977087567135d62532b67d5639fe206c5194c3f410ba75e1a04a2747" + url: "https://pub.dev" + source: hosted + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c + sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0 + url: "https://pub.dev" + source: hosted + version: "8.4.0" + analyzer_buffer: + dependency: transitive + description: + name: analyzer_buffer + sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033 url: "https://pub.dev" source: hosted - version: "7.6.0" + version: "0.1.11" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce + sha256: "08cfefa90b4f4dd3b447bda831cecf644029f9f8e22820f6ee310213ebe2dd53" url: "https://pub.dev" source: hosted - version: "0.13.4" + version: "0.13.10" ansicolor: dependency: transitive description: @@ -45,10 +61,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" + sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.0.0" app_links_linux: dependency: transitive description: @@ -109,18 +125,18 @@ packages: dependency: transitive description: name: build - sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "4.0.7" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "94eaf6708fe64408c632ef2689ca3777b112f9421306ccf4f8c84d7c5c9f83f8" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.3.2" build_daemon: dependency: transitive description: @@ -129,30 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 - url: "https://pub.dev" - source: hosted - version: "2.5.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" url: "https://pub.dev" source: hosted - version: "9.1.2" + version: "2.15.1" build_verify: dependency: "direct dev" description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" url: "https://pub.dev" source: hosted - version: "8.12.6" + version: "8.12.7" characters: dependency: transitive description: @@ -333,42 +333,34 @@ packages: dependency: "direct dev" description: name: custom_lint - sha256: "9656925637516c5cf0f5da018b33df94025af2088fe09c8ae2ca54c53f2d9a84" + sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5" url: "https://pub.dev" source: hosted - version: "0.7.6" - custom_lint_builder: - dependency: transitive - description: - name: custom_lint_builder - sha256: "6cdc8e87e51baaaba9c43e283ed8d28e59a0c4732279df62f66f7b5984655414" - url: "https://pub.dev" - source: hosted - version: "0.7.6" + version: "0.8.1" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" + sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423" url: "https://pub.dev" source: hosted - version: "0.7.5" + version: "0.8.1" custom_lint_visitor: dependency: transitive description: name: custom_lint_visitor - sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" + sha256: "91f2a81e9f0abb4b9f3bb529f78b6227ce6050300d1ae5b1e2c69c66c7a566d8" url: "https://pub.dev" source: hosted - version: "1.0.0+7.7.0" + version: "1.0.0+8.4.0" dart_style: dependency: transitive description: name: dart_style - sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.3" dbus: dependency: transitive description: @@ -381,10 +373,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd url: "https://pub.dev" source: hosted - version: "11.5.0" + version: "12.4.0" device_info_plus_platform_interface: dependency: transitive description: @@ -397,50 +389,50 @@ packages: dependency: "direct main" description: name: dio - sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.11.0" dio_cookie_manager: dependency: "direct main" description: name: dio_cookie_manager - sha256: "0db1a7b997a0455e488ac35744c68eed3f2a4280d3ab531835a65641b0a08744" + sha256: "4ed4669cacb11931517c1158876a2189f19386674b9dab498abcca063dbe4c61" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.5.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" drift: dependency: "direct main" description: name: drift - sha256: "540cf382a3bfa99b76e51514db5b0ebcd81ce3679b7c1c9cb9478ff3735e47a1" + sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e" url: "https://pub.dev" source: hosted - version: "2.28.2" + version: "2.31.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "68c138e884527d2bd61df2ade276c3a144df84d1adeb0ab8f3196b5afe021bd4" + sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587" url: "https://pub.dev" source: hosted - version: "2.28.0" + version: "2.31.0" drift_flutter: dependency: "direct main" description: name: drift_flutter - sha256: b7534bf320aac5213259aac120670ba67b63a1fd010505babc436ff86083818f + sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7 url: "https://pub.dev" source: hosted - version: "0.2.7" + version: "0.2.8" ed25519_edwards: dependency: "direct main" description: @@ -522,34 +514,42 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" + sha256: "9375211fd7df9c070504ac4db7047c7fae857e238291433b770cfe696b5c357a" url: "https://pub.dev" source: hosted - version: "19.5.0" + version: "22.2.0" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "8.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + sha256: "945a438a4779f3aca3a948718d8a4048cbe9f9c8c797492c00abd5d39112bf37" url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "12.1.0" + flutter_local_notifications_web: + dependency: transitive + description: + name: flutter_local_notifications_web + sha256: "516afaf97a2d1e67a036c6617321b00d205d72f7a67b6eccf936cd565f985878" + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_local_notifications_windows: dependency: transitive description: name: flutter_local_notifications_windows - sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" + sha256: "6f43bdd03b171b7a90f22647506fea33e2bb12294b7c7c7a3d690e960a382945" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "3.1.1" flutter_native_splash: dependency: "direct main" description: @@ -570,58 +570,58 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.1.0" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e" url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "10.3.1" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + name: flutter_secure_storage_darwin + sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: + version: "0.3.2" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: a5f35ddab43cf5c8215d2feb4ce1957851f28c5c37e6f04335066a0602087bf5 url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "788060052712555182aba55ecb5f8b6e5cb9cfe8f776c83249a61fe3ce877db4" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.3" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_shaders: dependency: transitive description: @@ -676,18 +676,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: ac294be30ba841830cfa146e5a3b22bb09f8dc5a0fdd9ca9332b04b0bde99ebf + sha256: "55973bf1b27790489f8710ea615fe939f911ddcc5986fe97305e4fc19fa29ee9" url: "https://pub.dev" source: hosted - version: "15.2.4" + version: "17.4.0" go_router_builder: dependency: "direct dev" description: name: go_router_builder - sha256: e436f0c69e717bd215639fbe27381b7e6605a255852b45cb244e28f01ab2e93a + sha256: "53253ae5bd3d3bfc38c5488ca844f6b054c4b905683667db4544e7ab995fddcc" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.4.0" graphs: dependency: transitive description: @@ -724,18 +724,10 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" + sha256: b880efcd17757af0aa242e5dceac2fb781a014c22a32435a5daa8f17e9d5d8a9 url: "https://pub.dev" source: hosted - version: "2.6.1" - hotreloader: - dependency: transitive - description: - name: hotreloader - sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" - url: "https://pub.dev" - source: hosted - version: "4.4.0" + version: "3.1.0" html: dependency: transitive description: @@ -804,26 +796,34 @@ packages: dependency: transitive description: name: jni - sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.3" jni_flutter: dependency: transitive description: name: jni_flutter - sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" js: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -836,10 +836,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 url: "https://pub.dev" source: hosted - version: "6.9.5" + version: "6.11.2" leak_tracker: dependency: transitive description: @@ -876,26 +876,26 @@ packages: dependency: "direct main" description: name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + sha256: ecf24edf2283c509ecd217e3595f6f71034b68888d28ad1dae6bfa0857b816ac url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "3.0.2" local_auth_android: dependency: transitive description: name: local_auth_android - sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + sha256: b201c006fa769c23386f89aa6837ec0eb8179fcfb212eadcf87b422b3f9a6a78 url: "https://pub.dev" source: hosted - version: "1.0.56" + version: "2.0.8" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + sha256: a8c3d4e17454111f7fd31ff72a31222359f6059f7fe956c2dcfe0f88f49826d4 url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "2.0.3" local_auth_platform_interface: dependency: transitive description: @@ -908,10 +908,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16 url: "https://pub.dev" source: hosted - version: "1.0.11" + version: "2.0.1" logging: dependency: transitive description: @@ -960,14 +960,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.4.0" + mockito: + dependency: transitive + description: + name: mockito + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 + url: "https://pub.dev" + source: hosted + version: "5.6.4" native_dio_adapter: dependency: "direct main" description: name: native_dio_adapter - sha256: "89a84d8936a108c206e481b8da090422abb1351febac4956cd4a8113627ebb5e" + sha256: "7ca3d04c76095a02c3b0d2e6f3c90e7781373671f7aacad34b81f31074a7369b" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.8.0" native_toolchain_c: dependency: transitive description: @@ -988,10 +996,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e url: "https://pub.dev" source: hosted - version: "9.4.1" + version: "9.5.0" package_config: dependency: transitive description: @@ -1004,10 +1012,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" url: "https://pub.dev" source: hosted - version: "8.3.1" + version: "9.0.1" package_info_plus_platform_interface: dependency: transitive description: @@ -1084,50 +1092,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 + sha256: "06149cba29bc46206f8b54b56065fe74b07602d2454a281287685ae66b5ab7b5" url: "https://pub.dev" source: hosted - version: "12.0.3" + version: "13.0.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + sha256: d7676c6fcf2f0b92537ec41476a6ead45a00b0d8bbb852395a6f9f33f49d6242 url: "https://pub.dev" source: hosted - version: "13.0.1" + version: "14.0.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" + sha256: "11b7e94a9d2fbee23c27f0cae0105c6266c03fd83b9a2eda6cf09141fc82624b" url: "https://pub.dev" source: hosted - version: "9.4.10" + version: "9.5.0" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" url: "https://pub.dev" source: hosted - version: "0.1.3+5" + version: "0.1.4+1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.2" petitparser: dependency: transitive description: @@ -1164,10 +1172,10 @@ packages: dependency: transitive description: name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.5.2" protobuf: dependency: "direct main" description: @@ -1212,42 +1220,42 @@ packages: dependency: transitive description: name: riverpod - sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.1.0" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611" + sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0" url: "https://pub.dev" source: hosted - version: "0.5.10" + version: "1.0.0-dev.8" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + sha256: cc1474bc2df55ec3c1da1989d139dcef22cd5e2bd78da382e867a69a8eca2e46 url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "4.0.0" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36" + sha256: e43b1537229cc8f487f09b0c20d15dba840acbadcf5fc6dad7ad5e8ab75950dc url: "https://pub.dev" source: hosted - version: "2.6.5" + version: "4.0.0+1" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "89a52b7334210dbff8605c3edf26cfe69b15062beed5cbfeff2c3812c33c9e35" + sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43" url: "https://pub.dev" source: hosted - version: "2.6.5" + version: "3.1.0" rxdart: dependency: "direct main" description: @@ -1369,18 +1377,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "4.2.4" source_helper: dependency: transitive description: name: source_helper - sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" url: "https://pub.dev" source: hosted - version: "1.3.7" + version: "1.3.8" source_map_stack_trace: dependency: transitive description: @@ -1417,18 +1425,18 @@ packages: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "8b4bd239bedd20ee628aed587b4c5b387328e85945c9ecbae19a93bdcd171524" + sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad url: "https://pub.dev" source: hosted - version: "0.5.36" + version: "0.5.42" sqlparser: dependency: transitive description: name: sqlparser - sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67" + sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19" url: "https://pub.dev" source: hosted - version: "0.41.2" + version: "0.43.1" stack_trace: dependency: transitive description: @@ -1537,18 +1545,10 @@ packages: dependency: transitive description: name: timezone - sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "0.11.1" tuple: dependency: "direct main" description: @@ -1649,10 +1649,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" vector_graphics_codec: dependency: transitive description: @@ -1665,10 +1665,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" + sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.3.0" vector_math: dependency: transitive description: @@ -1780,6 +1780,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488" + url: "https://pub.dev" + source: hosted + version: "2.2.4" sdks: dart: ">=3.10.3 <4.0.0" flutter: ">=3.38.4" diff --git a/client/pubspec.yaml b/client/pubspec.yaml index cfbb766d..d18ed859 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -36,10 +36,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - go_router: ^15.1.3 - flutter_riverpod: ^2.6.1 - riverpod_annotation: ^2.6.1 - hooks_riverpod: ^2.6.1 + go_router: ^17.4.0 + flutter_riverpod: ^3.1.0 + riverpod_annotation: ^4.0.0 + hooks_riverpod: ^3.1.0 flutter_hooks: ^0.21.2 json_annotation: ^4.9.0 drift: ^2.26.1 @@ -54,29 +54,29 @@ dependencies: flutter_native_splash: ^2.4.6 flutter_launcher_icons: ^0.14.4 flutter_svg: ^2.1.0 - app_links: ^6.4.0 - permission_handler: ^12.0.0+1 + app_links: ^7.0.0 + permission_handler: ^13.0.0 mobile_scanner: ^7.0.1 rxdart: ^0.28.0 talker: ^4.9.1 talker_flutter: ^4.9.1 talker_dio_logger: ^4.9.1 - package_info_plus: ^8.3.0 + package_info_plus: ^9.0.1 flutter_animate: ^4.5.2 collection: ^1.19.1 url_launcher: ^6.3.2 uuid: ^4.5.1 - flutter_local_notifications: ^19.3.1 + flutter_local_notifications: ^22.2.0 # sqlite3_flutter_libs version 0.5.37 bumps minimum compatible iOS version - sqlite3_flutter_libs: 0.5.36 + sqlite3_flutter_libs: ^0.5.42 x25519: ^0.1.1 ed25519_edwards: ^0.3.1 tuple: ^2.0.2 - local_auth: ^2.3.0 + local_auth: ^3.0.2 pub_semver: ^2.2.0 shared_preferences: ^2.5.3 - flutter_secure_storage: ^9.2.4 - device_info_plus: ^11.5.0 + flutter_secure_storage: ^10.3.1 + device_info_plus: ^12.4.0 protobuf: ^6.0.0 dev_dependencies: @@ -89,13 +89,13 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^6.0.0 - go_router_builder: ^3.0.0 - build_runner: ^2.5.0 + go_router_builder: ^4.4.0 + build_runner: ^2.15.1 build_verify: ^3.1.0 - riverpod_generator: ^2.6.5 - custom_lint: ^0.7.5 - riverpod_lint: ^2.6.5 - json_serializable: ^6.9.5 + riverpod_generator: ^4.0.0+1 + custom_lint: ^0.8.1 + riverpod_lint: ^3.1.0 + json_serializable: ^6.11.2 drift_dev: ^2.26.1 # For information on the generic Dart part of this file, see the From 48fcf2a91f579de9ddea1d5a6f380e0aa6c21c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 6 Aug 2026 09:56:07 +0200 Subject: [PATCH 43/65] Update pubspec.lock --- client/pubspec.lock | 84 ++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 0ca3b117..3d270feb 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc" + sha256: f8db46d2ea9ff6f3a37191a7fd5b7813da1253ace8c32c1b9eadace41d1188ea url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.2.1" app_links_linux: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: app_links_platform_interface - sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.4" app_links_web: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 + sha256: "79e05eaf15a48d7230b053a4363b8eaac0cc234bbd0134c3229455481f55cbc6" url: "https://pub.dev" source: hosted - version: "4.1.2" + version: "4.1.5" build_runner: dependency: "direct dev" description: @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -365,10 +365,10 @@ packages: dependency: transitive description: name: dbus - sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91" url: "https://pub.dev" source: hosted - version: "0.7.14" + version: "0.7.13" device_info_plus: dependency: "direct main" description: @@ -554,10 +554,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" + sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff" url: "https://pub.dev" source: hosted - version: "2.4.7" + version: "2.4.8" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -594,10 +594,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_linux - sha256: a5f35ddab43cf5c8215d2feb4ce1957851f28c5c37e6f04335066a0602087bf5 + sha256: "76fa9c841b3b1619fc5b5bc36efc7d158fa2356f223b6caeb1d0c80a54168546" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_secure_storage_platform_interface: dependency: transitive description: @@ -772,10 +772,10 @@ packages: dependency: transitive description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.1" intl: dependency: transitive description: @@ -816,14 +816,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -884,10 +876,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: b201c006fa769c23386f89aa6837ec0eb8179fcfb212eadcf87b422b3f9a6a78 + sha256: fdb936d59ab945c7af297defd67bd1ed87b11b6db1bc16d01e94677a8f1c38ec url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "2.0.9" local_auth_darwin: dependency: transitive description: @@ -924,26 +916,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mime: dependency: transitive description: @@ -1292,10 +1284,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" url: "https://pub.dev" source: hosted - version: "2.4.23" + version: "2.4.27" shared_preferences_foundation: dependency: transitive description: @@ -1521,26 +1513,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.31.0" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.11" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.17" timezone: dependency: transitive description: @@ -1585,10 +1577,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 url: "https://pub.dev" source: hosted - version: "6.3.30" + version: "6.3.32" url_launcher_ios: dependency: transitive description: @@ -1768,10 +1760,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" yaml: dependency: transitive description: @@ -1789,5 +1781,5 @@ packages: source: hosted version: "2.2.4" sdks: - dart: ">=3.10.3 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" From 807e4252a6374ea19f9c0424a9160d39ed5fd887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 6 Aug 2026 15:44:00 +0200 Subject: [PATCH 44/65] new ui init --- client/assets/fonts/geist-300.ttf | Bin 0 -> 90752 bytes client/assets/fonts/geist-400.ttf | Bin 0 -> 90768 bytes client/assets/fonts/geist-500.ttf | Bin 0 -> 90832 bytes client/assets/fonts/geist-600.ttf | Bin 0 -> 90896 bytes client/assets/fonts/jetbrains-regular.ttf | Bin 0 -> 273900 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../open/screens/testing/buttons_screen.dart | 122 ++++++++++ .../open/widgets/navigation/dg_drawer.dart | 4 + client/lib/open/widgets/next/next_button.dart | 208 ++++++++++++++++++ client/lib/router/routes.dart | 13 ++ client/lib/router/routes.g.dart | 28 +++ client/lib/theme.dart | 2 +- client/lib/theme/next/color.dart | 176 +++++++++++++++ client/lib/theme/next/spacing.dart | 17 ++ client/lib/theme/next/text.dart | 188 ++++++++++++++++ client/pubspec.lock | 44 ++-- client/pubspec.yaml | 15 +- wireguard_plugin/pubspec.yaml | 2 +- 18 files changed, 795 insertions(+), 26 deletions(-) create mode 100644 client/assets/fonts/geist-300.ttf create mode 100644 client/assets/fonts/geist-400.ttf create mode 100644 client/assets/fonts/geist-500.ttf create mode 100644 client/assets/fonts/geist-600.ttf create mode 100644 client/assets/fonts/jetbrains-regular.ttf create mode 100644 client/lib/open/screens/testing/buttons_screen.dart create mode 100644 client/lib/open/widgets/next/next_button.dart create mode 100644 client/lib/theme/next/color.dart create mode 100644 client/lib/theme/next/spacing.dart create mode 100644 client/lib/theme/next/text.dart diff --git a/client/assets/fonts/geist-300.ttf b/client/assets/fonts/geist-300.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6d65f0f0b712a8a58874958980ed89ab9d322e7a GIT binary patch literal 90752 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�yrf~U_cZZiQ9nR=> zI=eHJ+N`jH3u(76Y73ZrqB64{Rn6FZ(mWpHE^1k0v8-t+bT2w!aV8`HjRAXq?N<0>-n!7qB>ho`7-XseNH(BFFbxwz7LM{_+qSgru$LL+4V#gfkA$K8# zX$uXjF0oqEi}1$do74?eE0+{y$rptcLMhV7LC8X*p4>^2f>yE--uDFbSQJ6+)8SNu zFYn#+k&R@{{()8^Ru|}h`WC@yKj$TD;A~OLx}u)R_Huxp$TF(vC8Mz{AjGuO((Yxh=EG`5-=Y`Iw_mBl5FTK0a!sC{iu zO<%B1UC>>$V#CVf(zK$u!IJ#!qJ|0?1D8Z^>0!I2uZvm2P{1h8$jgm&H;g42-SRos zP}d|eS(_T)C*}$286~5zN;#`+@T~H{Q85F@)6LEB7ODR4su})TK&#=EcB@uB3lqyX ztg;RJDUmXN_q(Z=X5b=SvP>sYOCW>wTr&4G2xxc!l(@ZLa2q-{4c$g0m*TO33-H)_ zi?@xpuY^lbS~)f%Er{q3->?#!zkl3N(_F)fr#`qrQ`d|d5qSa*(veNTNn_GaxQ+-o z&07eLc?;JD0VQn^(4%XJJ^>|eQ676%s3!uVF5;pJ2kuZE4S;)TK;7h^ z(o72Xb42rK(4hd;KIw}wNhH3E8gnIB406FBa|Ei*LM&%_PA;xZOc(kqesB+eH3vG{8| z-bnng;^QN~!InDrN;cTHQogH?Fo?X=mai#oxxg_CIAJC@Y(1x1 zK&dSv_Yvjo#m#Jk>Opt`n5A^7;bE(n&R;zrH#Y?J_2K?yK8u<$d$~J}>ZZBXCB8iD zaw=@ghZqCnXqWt)D@NjoK;SyjYqQsh27~5li@tDyNPc2RP-?L;U}*^Shwc?@KWd!h?te3!j2l>xF+@zNR%W*irV&+A@ zVKkrT1$c!#qVHW&gcIdk3JGZ%DaapbF6}~4KPWt`pkblKv1EzElJ9~m8V+aIz0aMS z(LD9R<>9GChJnJ~d~3HA|L6A>f(r7;^}L1mE1L2=r%u30`ULk!?U0*mih$m)#PUXg z!<(0+^P?{*^MG$fLr<%P3jXiGSn*46oKhsKa^{VfF2IOPf{i*X$snU2_IqYuJA)iQv?n~B6;3y-0kGY zahkOT;2=#AxbMJuP(K!b`;X&@fjFUp^DN$2T`!Kbi4!V#IFEM8u%{G>TKH;rFW`KQ zUxDAIdXh)wtMGnIezJta5v5r~bz)>>^LUhouxQlZdPl7Bj?7psK98GuxOOOOz4R1x z^7Ux5+WRzMrB?cd@>HWft3-#X40xfeVe>KyKcji>&v`4(sh&}IF@n_sZ!Dhb037rM ztcyrLL)&RR26e3QuSa;HS~YMf>k)*DX_*@C*$eu7Ht+b4S?dvu%Eiq&LE%^D+vj z1MT2>9%1#{&XF0G$@?_GGVz*(r8uj^%czXsQt&dU$&R7o0p2?ofm$tYbHU1)0?+3* zi{NIK$}Xqph+N<1dwz33xni8WqH@p|?Ctl?y0o~jsv6}aRXW?n_F`vR3VEhom|^{w z9WQ*v*}K=^WKm^F(ZKHgMJ4q$J0rp>m=uwm6qJ-*o{u z7v=4ihixKM^J4V-TA1QM<%ZZPK=Q8k@Zl_diG%SpAs!Tao`U814 z!tf*3DIQm(5;5Fbf5;^@sroJ76DZ(AVh^oR3s2ODGpW~D$7RUf@^cxx+d+3OK05gr zFb_PTxjLP8w(D})5%~p1HL=uz0r@qhK9op<5C0dGh@g`om@yO;*`tgIcG0#=I}WU> zHJY@(=q160>`c$N9KDQ{zn2{xGx=#uO%{7&>iDOVlb=c|d2dd+(puUVuOVN>`NCc@X|tD8fNPWBnaC#Lf1#qv9B;Z^b#W@+$R~ zV0TwzM`Oi~9o02W?cI%<%^KH^rQJKr&Fy85jts|+NZVkUrm(!!QChqrr_54WR-`Q% zwA)vdTIw=2y3ovq!qQd6Vq{iM!>NW$I);rbII}1n9+xlNJkA=fqceFR8Rzp9o*kxh zy$pNiz_VMhbBInIb%JMKRo{r24}xiaEU7qOBYoA2Nj}rR#pOp=ap;hu%ey&OF>6wI zN)&t~f1ec}#D{z8_dXW-eFkYa*TN}93#YgiIzh$tz@?-47iO1SSxnFpL@b?)b&GXU zA$xlKvusUbY5tnML4DcNP;r=&ze)YfbCq;&E zhZ?PJMJjEuV;4Rq$97DUwVT&O$A$iTLjEwi#FF@riRTxOM8rzvQ-^c2evCL-6|Kq% z3?v~*3*;mr>=uUhf_ilcv`vTTG-h{eyuLc@245yI*}Oqqud8m=lN_$xIayaXxpUpF zTkGm>-PJpinK{zCu5XxsH8$;UZ{Ob}K1Xu8?Dno4@%g0m?$i=cgFZt$^3lG#sj1kX zI)-!D)Q6xawKiNvp=VYw4oMm+xo7G+Xx#YJTa%L`pQ;C@j)RM(;551m?x4F!mUPG$ zeufp`8=#8pMCND73oBMtv~IAE*EU+3>`~)vwJ|y)K7XizsXwlD>JxRWkmEw{RuhL& zzc@Kdb<>Hm-f;^vjI$)`IOMm%LISi%x1tQET8%ycs$tp%)mX;DsatPZ^@@_ypWo<1 zK;%!(g@tyMLtnx~-Bdgn9KgxohH?4(t5Jftc=EBwIIr+QC+X%EcJbH*eXAZT|mu)z(%C?NcuS_tS7wA#_1iCBYR(d&my|mGdBm zD2XL&I(z(h%lao~;P}1g^d_w%yn+$J4lL+xL3zMJ8l@VtqrcJ4D7s&S6v||r-2(<*_wJc0 z2{>v$Qdn8;7}-8L4l&O>bWgoUISUu0!C5h?m-_s7(Q6E}$`Y`$ILD1AQ8;J8CWa4p zbQ~T&ct`Bkh|%`SRk-z}vTMWqtr4A9N67cGWs&{&ZpGCn*Ilk@>#f{FSD*AR)plN9 z&8k+RHX0G`3#?5!tL|M~Q8r2P{z@Ba*l^#$u84{GV_l-Aw)GKP7u|l4(|}vkBC9X& z)GqC>z!fWdDtp^BmtVL1-ifXE_M@^@LOb|ON<(|U@f#LNB?p}4e+;)&(Y|Bl{5*Kl z%6WphmLx+Kv9#J6xC;cMve@&e)lne-SL4YGD_8P9S<_@`E{c+Wfg3NMlus!gbL#)% z{ZjrF9e=|vNNbwWk zO;)9GoiW?Qg#%vR&;aY*I72rOkRU&dG>3(oBAeT5S~42!y7BRph2{NaEt|`nJEoqM zk|Nc(=~}|(8Fx%dl>DmnkbJi>CMVIcvB|yBA#DTu&>rg5zXkuuUcgck_hxtMRMhp; z@sqm`9ol{J_NJ!h#@ppj*jv&|vS-KUE!#$%)zuYG3=BXPWVhjlaZCxSBw≤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

BPsR2O)Vg|Sdj2=)mVBvtr z2kaSee4sI~?ZEB>^9BwbIAP$-fmaP&HE`p=_Xd7C@X#P*P}CsTpl*YT28|w6KB#`s z(m{6&dT`JygAV1VT+09~}Ao z$ZaF{jr?&`*r=YPrjE`UJ!bU8(KAQSAH8Jss?qC5ZyNo<=x@iUF%e_hk4YVqIi_Gt z*_f$g7LR#o%)4WDkMWj9mi8#kDIHuos&sDY-KCF|{-bnL>AR)J$F?3jccN$`+K}T=uuJtz|pPJ}TQ+cDU?B+39hPaS`KU$0d&IGA?ag_P7D# zO2(CqyLjBpaeu?=EQDK?(3)=x27Ir5n1-cDu%~HQi3qz(!-nwKj%wHz5XUa!Z9BEU zLnPQXYB)%&u%&7^SWGtF)NrV98+U3rEC3Fd$!f0lj}X~vkcOi~2zVc}o~=Zb>Z0Lx zet+82yj?0|GG#G;;Bz$=vq*z532D&Qe)#TwH)sto|Gecotk|jv4iK zBWm3s`iS08b=-nlV5r$oCO%Pn8<0mDeCshrW^Xp2oDv<+CB_1pioE7=OQ@F!16`;M zVWgDV zn{tex*+=CV7aC<3YFy3rpkAYL^Tin0G~Rt+|G6G&o@xs6qaK_EP574vS0j3YW=kFV zz2JAlsm=L}IrZWY_i6#!PoqmSpYW1mjNm#~AwQRx0GQ_VG`JSRK70OVpHe^9aBgQW zZ!qd#%~(vmMk7)Wa{-row)zCl(dO9M(&*CpfSbqIg!%#pN<|@Xrf(A((mb!1m9fzsm;1)9zWcr|0&7E}ZK7jhBp9jDOqWZJlg=Y=yQ-whG%!+Z@|pv4g)Gd-!K;&tVt;itTONhqljchiu)y?I0Ww`pdid?f@3tZQ`9(ApAJ%ydh^R9m;wM|M+ z8kaON>8_-Euv2*|=^sg7Bz>8zk{!vB$Zp_g&V!e!&X);gd%V9Xjpcb!@Psq3A zeii3y@fNkq5QamyxQmg+Eq;+(ENty9v6mF)rBdfNuu3*6#e zwtcpPwxejVD{(PeyvEhq73WHDxzOTNwAkay;}$o%7B^{eELuD^>7t|+Xz|~Zo=kcs zX}_<`>^c1q_#xkO`j^u$ z%V>$yHT^6XOT-m`>rd}L{lV!ir(Zk$oDje67vgld6{V^D>&#zG{uJKdAEvOq2ZY%7 zof&K2mVGaNp7D9mzC-&K?VAQQV&8rH?%a3#zFYR)yl)xe#Ug$!^v=Ei*}G-$>wA~) zy>0KLy%+2q1;3KL{r9HtP1_r}=UX9m=Y6{V)0@p)$^7)WPk#8g-HvZ};A}S}-SH(( zxQ+=S!{lvpotmrGsHcIqTh%)h7Z}?e=T2!xF1hG2vW-4QkuhAmaNg^GjA01B4EiDC z6Jxir$2eddF^(F?jN`^B<21!1+lHf0I{viTV1_}zgpuFu&!r{E_9)_!UUz0_Q$O1S zW(?cCwl%=4wYG=+DQs&@SnX9`8!74soET0qJVs|D#0XX28N<~f!>$gh6Y8+~${1^m zHcHhuMmqYZ9e7fgNCDrNBL;%TkHpGVCN2c^pNv&)0Vw|>ajjS;?h*Hj2XXqh&WKPy z8j;2XoZS6Wd@g>$8KA<6Xq;>(lVplamsvOq><`*eBrlSe$cb`_td_O%N_myMPA-)z zKr#9m;l_BiPaQS#jbTOsPBi-)nK=E+l3(F0@Is8k>qee&zWPvoWW21dGBVV+2IihT zVkD_8>NWL-ItH#+fqQL>6V6z0&mAy}JBzO1RfmcqaGxcj6HbU{VAYxeN;%ya37WY= z+#qfe%f;Wsd^ufwkMqkT;wN!joD@F`FYw(dgJmS>UKiO_rb@dkkRxTWERh$=;l?OA zUN*`)`4?F)ua_6g8$dx%fRcWJQ`8?td-1DCltOfr*m;T5B1sw|87IXa>>AQ!l*qsi zqKga?eQ;WxE8|5!nJD_o4kBN65`$#27$JL$^JFhET&9V^va=|}E@ZUK6k}zM7$@_@ z1rlqO>?6j@e&QlIP+TJO#l<*DpCk)KgS;tK=fFMqVQxlFP)y;&FM4 zcmk)~>*cMYMvfM@%DEy~d?f}*m&n6Drc`E&tK~Fty_^N=_o}hY*aXV|8qRMw8~-wH zH&)`b`5xn5(8t?wqI{pR+PK45g_CBSGs_e5N0lhQ#d&@k(AIV;9_Pd{s=YidPpKdk ziL>;hI6ponzn4GAlkz9|v;0N=s)BK%9;(7rxQbBGsufPw+tRtZ$_91LRe7qv8ldu3 zfhtr*ssty_V^x_Nr_NUwsEgDDtXPv(m71ysszGX+>Z2y9iK<*pQ5EW9oV-`!jJjBj zQR7udoK(A1HO}g1sAM%$9#?&Fp5IS(QZ*_?%~G9Jt?HucR97`yrQ)2vo2pmcRfFoO z=BQqvFllO@>aFIB{@A-cAeV^y@KS1 z2r*NR5)~3$g&ZoT%3)%fJWo{0A!3#sBkJT>F-zv>pgN9s5jkqJdR@I~ z%))tnwK3DEGiDpL#&lzbQDaOpMi}FaGGm#s+_=HG*|^bIVccZgVq9-rhj$fE%G>ZR zC{?{KTK~`WzeObFY5an`pA)0iPSK-9y%}Mq4uc)0rosLHtG`59U_~&1!%DYZKM9C{(YgTmjA~5JO{0|A&-`H;JhIGZH||~E#Sft-~{7I zlW`2_D{7~&O{jZdov2*fa?#p$HTt(mbp5~5##6|9mMHnn{AVG2E63@oE`mMkf1~B& zB16?6uiw(wA?!lb>q4Q_h2F!Ur$x{uruH6!9ijcmUqNTcaPJA&39vs?-4Q?2`)i<9 zjmWRjdx&h=P3u}8Yy;0o<+ZG-?f(bb*oAzl|EEHuujO5+kEsX1&QskG-jZH`c#nx* zte?_0+-e;1`8|CJ!ducUr5PgH7=zE$=)W}8N3{7}og+LdKucxzGxzThkqkQWzte=H z|3eMFX*BCvwhI8;TAUxv=K^pU_*V@4+h^JYiv-|eG2vko{IU8QV?llF>r3jh=6%z= zZ9ib{tcKnqG7JM_{}9%mf9Um^)*D(^y9VeAfme1SC6G=*N+n!f4L@uBIV47?dyt0o zUXjCV6Rm@^29Zr`2GP2Uuq(X_Yd5sf2sdc6VMo{|k!|c0qw#iw*3QxD0j!(XK;vA* zHeKY{M!}EPl|4waM|1{lr1dk7)=EfCgc_e9{O`zP0N`k}ai?G7Jx8KiD&hm_oAC_r zJPCbNhB;&D*?>te#5f_nngC67fozM8_JLc^54684&zFS=ufbTK4?S0>^8mI1o+~`Y z8tBDR!MEE3Vz-qLJQD32cRQ6EJ(Z>N#L)aqdk-#U?m%?ua z$1fp#=C)BgC=Jz*@QiQ^c#LugS6g#g|HAlwhd!DPoAC55(3QuqJ9T=|J zHTr4==+iX7@ui=Zz;mgF`jtHn+u$bF>;{iiEf61-50tL zdIKhBE&}Tu$lZgK};LT##=ZP$w9`yj8 zvNo2Yo@>#zW5A1BpcCMRws>^gjev>onv3`=fM3U;UxVHSI`JX!?rr#engY9 z;TH!T1>F-m#iu4gXMJnETrlQWNm|MZ>2F$k`fa@KA+nX^zyQ7ai zqO0w3^Z||YrLdt9)&u+29!3J}1<>S%&2ISHdT76;uvcq+D{RuY2k1LY*cgX-{}$lM z&_A1YbGLCZ#%DXq7~1B{y?Z z7{zZXE~k^XA^m^Rw_#eZhrLYe%V4*GUJXq+i+P39psw;xGY!rIa&VHAD*gr9Kywa9 zOQ1L#jEO=Ol?*SFvuhyb2^v0Cgyc^scFEMj{4$p~T3A{J_`LiHFh}GMafxR~j2i9| z3r7wwa*08uqecLRs4YUvO3#BS1kQuwk+&2IXIVC!JUDPd5rz|r2oZ^swN@e;rxr0d zd<1!kUyO=_)n({`qL?$=adrXxZGCA`ZnpMbLpeR^75KSxu&AL zp-Qf*sF*cd-dI^vJxwmDtgV|R7f-D(uaI-$QI7GftEiDP*sNr8BAescEUle0t6mPT ztFNq;MYG|M0~)Gpr^?)h26wtlgXxheFf(L4%uE>tGfO&PW{XoWbHs6&x#A$qJh8i> zA>A!@!b}%$Hq4pQAT~A3ncW~>YDCVU;MC5JY|>nizyOj4?u9o)0_RZ<`1Ax6%}@_O ztr+SKh~#jkz;!Og~B$#&OL>TVGIVSnJU@C^vBYqD1C~SGy z+_1RNt)Ux27eS8?9Tu7zsvyI;5t5uWArnFhpfVxn85d#;=@R@>@cQ7{!6Sov2FC>-$CfsfKUf&XsYG%X@so{2(ybJtm^TGNXiufAvUhvc$^(ea6r^P zvn&I>J_sky5`2>73;Lq2Q8%H>9{?90f;SNdL1_$(K#a^qOrqOndW_PUF%3KR17V0S z(LWOVJ}Wkk){t%^dT3)a^*%z(&{gQ2RZY}JU)Unx!=eDS+80uZQL=ia5`AaoHXJxK zT&o#A-)!JkHY<9&W|o@_I~i(_&nF7)jY6*x=BdC|>J+JH3%zwyuqC)*%1NO&Arp>t z^pO8GWXdaHk{kVjHy(2srwrtssQc*v{H#R*W2r||A3O1zHuIMLz_$fS1@ zZ{l4}53yVJk|p9ZIb04GKjDqeNb$2gU)GCX@II#zZ>=tqH{i|HO;FwN*7q*i9dq$M zyu(@xl_MXLk7G_gEuWEt zy=tqm##p1?!CR9@)w{+s#y`}DB)6|V!JD#eYBxrT6s=Gu7F>mqUxmzg%5D&fUx0XV8f@thtI?wxmmuBk$3~+@NXW6Z5W5`@@{vSbTty z_)vbtWAQ0Q;xka)y>cJ!DeT8s98ei5Q)Q`3)n)2(wMhM>epbJzQ|ecBT6yulUm6DF z9MJQ|^TwOTzftQl%nIU;I%2h>8PQwx6D61pLqIX6W40~_Zr=vneONpK9DM;8|B`qe zZ*`6WXTOtDh7zvIRx%OqOH+Y&-6474#+%b2z|vB@VQi4|<+Z@e8|7c+-H^OkEB^*r zgnt14p2xehb}C&B(r>R0()(O>M18G}s&8b&LKfUIvHy3hsEM zD-Ys@1K-SduhbJmK@*1K{DxrPMt4^%e}(>RBOjCN zFb~(G51y1y@oan+y|F<)$360bd{Mq6|0!R_`H4TB8|m(lcVb4bLLaV{_wc-4gWh~V zKFB@#5bmlxBL6NQ#Xd)2L_fzqQQ-aCH>l&cpetc`!}}AYF|)A`qZ>~|X-QuOc#0U0 zK8gV30$&XIR--%Sl-=lodpKCDj9zS)8a*+0$ZZETjYa$*oGB@z6emy=uT*`Z!%LB` z4J)>i)rc8GG36n+a^*poDH5q<9LI^1)krxupgxJHeGKR|a=?ySVH613t07uTmO)$N zjXDEEo-v-qogu^nzB~-6okx8!q%1(2toRr&?;hkN)mrs9^6*m|q6pL*He(Q`NDih1nj5cN<(%oe1!Ai|zrg zWUP1oTEsvK%6ts_8JW`S*26{+#(P?nE()7WM zGIKY5%)XUKN#T|&7ArILs+lv53hthv7T+T8ScDM8qR}A}7a(btk(*7rMQ7zi+=*R0 zC7#CJww1SOVdR6i5{ zsJ0SuEdJ8!7vd6|-rS>jDLNkY+GL!zMnihS0pvW1UB?l50PEl`tdZNWe!c=a`ZQ?l zTF}Xra)n%m-N+)WLG`jmPL-3fCKZ7a^^`8~RYBqu&bRhs4SpZ%^H!`HFC+h6z7_Cu zgnfY%j4we=4>Pa!HP*dvKvA*wfvTQ>yv#{(Vk9Zl7i&W{aRO?Ix=t-s*W;e(4Y(zE z6R%^Mk3h@X80}!j7;!LL8*O1m8?i7I-c%U8;sI0D`)ZGRRXqnfWmoU1&v46dgIWi8 zr`oOlrT&3aU%UFR`c%D)8;Fkr-l0BG|5VT54S`*~t3HNg*wg9}!0)J!)QjpV^)TSK zA%paSdQv@v)A8-Nv-rGvLj6rW2-jQc1GQ1D2VHysy!-;JJqqh!CPwjgxd~>5+zGQA z&bO3&RK5)J@A988ACWJ?d|18+^C9^HXy@Oss=onyEk@q0!F)h&fZzRC=U#_v zjeHj7eexNY_hOHxu$}?8FkVk0*4@` zN@4YW2j=VGuYB`fra~&@t1~z-i!a!s*L}U>gRdtVaYS=w&xmKmKX7sYitrm;S|E4k z=gs~Fyn7Yg^h#i($)$mwoRw2^D^CEg))MZl#~3|{@p~F$`z*$N18|}lPp3A4ql+?H z8C{I7#Ge|M8J8Q2j4N;l^-AL^<7(p?<66z}tu^V5=KO9!pEc+GEN-d4=6^KjgFO&v zCuW|>6D|~&f^)c>Xl2X%VH3X4&lToECbf_32f5TD%^!l&o|Q+8Q#}H>#I?ZLht$Kk zd;53wsCrDTQ;!38p9f~Y2(14nX2icRTVBPkyeT(%0yB&_dY9(pr$N%E8nQk!A?-5@ zaz7;fWAd60nlzJnPFmgh@vi0_sObnW@t#nC!u=a^gNC$0(#|2Bpet|4U>O45I1Fb+ zQ)C2W(3nexRP%c5B0J;k=vU!|WK%TGp}K++wGoG~x2ck`*y**!TGCF&%l2ZcOu$*{ zf52OJ1iea<$>7nui<7bwC{AZnwhyPy;JNW$r-$BY{49Hlx3N!5lf9)I?>^I|2XCe_ zWG3XWvSc=-@b+QHn+_U?v4D(KU$hH*6O0PUIS!PAAaj*3{)^pVAx4R01_xuThRR_A zQgqm#W{9uBGi1s0z~7G$+1P{b$NqB^XS)6ho#$I7I z-Za&39(pQDSw0%rX|66O8yE7M=us? zsgyEE!dBUo9V@a^TqG_QM^un_!IE+mm#A>@B_tgqRTN|$qgg_-4J0PpYKh7AEc=)U zc|{jw6_X*Km;%|vE|5!1g-l|1%>JH`M@)k(BJa-h4%d>E?4$ZZrjaBjNggs^6|mG~ zG2|u(t08Kr8m7)u!_^2iQjJohAr~1aO*tO2loQlNEH^n3a+H&4Cn{bMuc``?5muEf zXE{wxhZN-uH4}1^vs5i4CTBxhvL2F>jcN|$B zp>9#Ps=un+)a`1ex&vPlxJ#{4cdOOv9(Av}Ppwh+<81LktjA~4x|UXimgwIm$QF}) z;}%F5zp4HWN%8;SO9gLX<$oJo!MorX{tIs6J#ZQyfER#tGPs6Mz)|c5x3LG3*Zb7x z_^QDd>HyAj4yr@VsOh)rxcW|guYQ0O_K)hM`U&SJ1`$AH?KgoVV@-rWD+J%sgFE$`sVhjP7JQT9q z=NZEx^*Iu|kw}~fCgOC@1}X7moY=i4u7ecEDDbnoUl|-U#{E<&cD30V&v9d8ND^lF@etAtGuGV zuGX1fH?6L=YG!C*MRk3}oLN(As^*6jR@OC^S5#EhHadzb$`K1zeO-B@qnKT0UIn_2 zg}SUlop+(GV_{}Uu`eH;L|3OE-&w4SFE)$U;aq)pdTxnhFqafM*k3D(;!gK?LIyX> z)x!n5(~C20L#CA1hYslLnfdMvcY5eB ze-37;#awZBdPcTmczMN~#wy2fUr)fdz%kPF9U0)8nPn8$PBV)y(tT5;JD^DSQIQ_E zBC8Me_!So0M@_G*uhpq_<%_LUI#!V$fns;~sOfWRrwnN?Fhr_njeY@(yotW{}1 zt+KKmW7#)!Y_mps(skwXifv`+M@LyevTQEe?Mcsfj0?ciVqO2@Z2LHWea+rVFD}uo zF1G5g>s?$NKCVf9$LS#%XAX&dTzz%zG{-m|7{>)>buaK&H~50e>Z+@oqXH%qdze-Bc-Rr?pH{xW^kR=Yz)`@B%ZV(Bj5=Q#TLqObuTy`|8PHxlQ%5BzLRyo{T+EHX?6fvqv zp=NE1xy`u|<5~*K45&?s*{ld3#&KB0g)O8jG1Eo*F^l82ZaU2D7t?I4*|SyPfSb{p z4GDV&!!>BsyJwo*6&C zK-)T>{0wKgo~`93>V;PL5j&*9hs2Ibo*RxT9~DV2F3^=L)P)r4Obd183bR70d>QEa z>UtCuIIDEIRc5(5oa^oOWaT-gaY>=m{MDf-STZs~rZvme!v$l>$g)kRC1bijO>i|& zc!=tl1r+KID9m$K>k3qx6>wDZbW_zcxExPLj=6|rn~TT{f1YMNin%FRE^-_-OcxwA zzP^EPp`+IHtqt(a%r>fgi-_*6BHblLy4Q;I5Efazs)w?$#9rqo%0;>z#a1dEt4NPY zae8=N6QW#aw$=f88Cvk_18S9(<7i;t(1vD>^rYw68fo!p42Yi1MPl(NaLfrn)nZ-m z;vD-Ne?84!!s4M@TWr-^*SEMNd`^>k&e5YX#~c;=99}%;n2X0evzqh#)eN3z&0WVl zbMaW<4>X6oB+IcNaPiRnT9W5j$k~M~Y=TZ$JPP&VQD`j|dhy7%+>UaZyy>CkoPmaY zq&Z#F&=L+X9R)doj`ToBmd`=+m>p5&{zmvhF{}MiX^IDi1ZJOKz#$RkR@VeZG>Z-` z_pLzbVVVM%u{0TAM@Tt!K7*mvdGK^_GKMZ#lU2mV;aGY1(=#%q%*f^`5}i!(n0_HQD2DJ(}lhy~j~Qp&6m(q}MP{ z4~D7rH2rlzLrr;J z1$pd<^b<{XJF3}=sBV%)gpXVXFD!P4YCuiZjge z=Cl;XNTAzRVm3U&H?hrh3tC85Vy27qPhgH~F1>oj1^DP0!#=H>PL9A>T)tU}z?@7T z!}Kw8GJTrn)Fc*XV)_J4oisI##1w-XCp9v_t4L)Vs7yz4T^!biF)Qy>)z#uBS)W)1&L<(e?7^dU>pRS^4YoJ-R*~ ztDaUo%WbvaDo5AXt;=!ia@@Kcx0Rn&KV6<%*I)09GP8C5Iodx*=bxwZ&C}u7deel@ zG`INf%mO_Q1=`=-^21-pH@DU9Ompk#&NMfHuyy^4iP+EK_ntQBJ7 zX<)QmLQxE;nT8SDtYU_)T82iE3|++xJ!DxHs_F1N?at6m&(K|+rSr+M^3h$Ap}RUm zHzh+4RhAx-4BZ7;dT2BBP-IwcE1n*j3_UcNy1YzXUZ$>hrd4kpKU3E;Q`a+7*DF)k zD^u4i)2f%1zb-#h*C*4erxnj~TkW^X(e=&J%RP;fBzuC_%$voV9jvKJHv|=-j#H(7AKXp>yY&L+8#l^LOW(L+8#lht8dA4xKyK zL>+goi8}5alU%q9%<1dal-O-f6OY@Zc*;=rg@m2ZJOm|`o=cT&-89J zZBBJfP1USAU%}BnA79bbI(KnKNmNZ$LxV;b#*nrF9)bReXZi;Q#{~vAMPb(}wiVB*uVaV1KuZ{AAVUjh@F^}av>BTu=y5@wJIkYz7_=DHEoJfBg0t0#7%Zc!{CNPY*LOP5wGaysu z?Q|HmkkgsjP&3)dZObYwanMOX4UeWLtFX}7NEO$;n+5KY-MVYCOzzT?rJI^%vIKCO zU6xgt9zqD20q(M50hIvIL%`INf3Ks_r@z0t`?F9^f&%ap2`x_SOUPd1;pWO#fhE@tL> z*N~xi4H=px&B)M&YZf#kBUe{An_mFwHUoAC_~!+{85;Hk#3$H_Pp}oA;0$J+P(Bkt z?Z^pqs7po&xSd;!X{uu<0(O=u)wuzDC4jKF<{Kd@r&K-a+HR z-A0OQ!3JOn$s}WDRei0wQzXoqIcp|lr%7UoZuC*kh=DYAytj7Z4R0`f=&l*W zCSgW&c61OiDXl(=SY%rVX{rR=?O1}lYp>v)GTrJjB{~r|q7UR%WcX#sH5hU|9;lv> zUvfd=UOV#IY|x#a*Now8l9c_+#v+*iGG;M63Fbmb2#{Roea3wd1*QKsko3G+2IM;v zDk1$O6MCWy8%w1^E)_ARdZ)mg<8{I;_fCar({GlaN2{n0AP;KnMk@x{yqEX6oG+*TtAZJtUKOlue(R>_f8XzP^ceQ?9KJ@;E8(8IoQm z31ZzM+*?G8U|PM?Q{LcDAHR*#$KM87VHz*0mGvi*-{>J8l~<{rZTEApjAVEh!*uJ> zDw)1ih#vC$phfUOix4O7e2Q<(1niSs#<$ikM)+aW=xW?9v{VtK)Xr56fP~xG)i9l( z88a7>a=AeC0uXk1z6eC?eWtL>7DS|v8wGv<9lMygo(7=7j?EtIr zWjJ6Pq^8Kcf_%gQx>YGxl8;=@W-W*2Gwh`}@>4cv!$;wkX&Gd_1SD4GVDy)X)wmh= z6jU1IVs}AuP)QqP{^&jn{TpQhWb>-!Vo?Tpy4xWe_b>SxzR>ob{7BS89!!b(__|%F zSO96RB+UFYl_oBQ6xMm-GJMCbOk57Bsw%Mv5>+$A6_BCA-&i0+HBVg0|ADy*wP$P> zU8%0Xg8ThA5WGI9r;tI%@V&E(gW_4mz>Eos2nu4Cf~lORoF~{7?>t7f^Dy*&=Wge& z0JXzus%^OIN@@%1SDY_s_crIV8rtPt-$Xs)e1O$z=St`50JTEH%bcXHVdW2Bguk*_ zsviGnAvKltWak9!F4K06(+|}+M>>Z%3!INQ`)QT!^eeZsyHBM!1688W<%bZCo{00Q zXlGb}asb9c!D_X0zT;=DzQ-Nl=IW@!r*u#6^7Y$#-4A9P%{s+#IYQ@i)Nzp2KA8I) zpE%xU*E=xZacp&LX4gT-%i2EZ*ywoLu`WP87qK6TO_sE*r@XmtR#KjesGqNVJ6@P8n`+UbW;_qq1%tlotArhOB`FTs4tzJXyg zl*}htJqq(t`&x$Yg?X?2PKM1;GH-dVFp`e^MV(+taw#sIRV3S7V>O_I_MY}GtX%eZ-GYtw7>p39 z1gLd{Z}td#5Y!&KvYoP>vY)h_upP4yO9MjxF- zF6)pIJ&W84)5bgJ+qU6$`Y%hTFu4T%56Pp9^bwDZU8$D89$kh3MHaoM)G!Rmz zM8~Vi#GOupA7j(QW&)cX*oqL*qC!jzc?u zPn7uc8nv+xo1NH)*EZRe;Z*V$A8_aZ^$ubZ=Ox7_?9-jiOg4|Q*^yJZ81BVz8rLC> zI98d)HF=pr#S(CHQasKv*J>Zs2mTbgRDo-iy%^rbEluK>z0{AKPYs*km62y(G8MRE znay$1*krCye#CGx*Cd&1lFT(p=9(mPpL8DIh;($SyK*|bE?a2;DZ65V)JVzx59@*?^# zo*F{3$m)E^A{XM${9t3Sx)4&x=cx(A38{;~l?ll23P=?TLEo`>NZV^*lU@aV8}xGM zrO;PFW3$p2!w591g50%gp(K}30#5Te&v6;=+*nC;hJ<3Xk7qYpL&FyS1 zW7C{5uQOcECetJ_gW;_(@%0P(0s!`VXa23v$JHAn)ivPphKMj31eyACRW*n?^{i?# zB>G!Y6Cm|}b_HJb3}x2OD%s52%*l*9688+wT@Ar)g|n%CbZ7fFlpC}5TvQ772Iry@ zG2hNbSuM2c9#}uKK2~W~Zq{hU>G3doYbso2=r1#$Xl`Xc)HD%3bW>fbmw~C$$RrbA zY$TI*2NL*6{-Xux6z=&0#tq(+nE6}C$LK~TZpOl2-pl6Oz($FCgOoO#&Gl^3%rYqj z&9xNVY-)>}ZzFITL3^M~+!cHU-$Hmp{2O1A|Bu*?UD7+qWjyW%?!rx%Pa%Ci5%+L@ z7UiHRZN&oI%W{hwaMx*qxKq|*hjbTi8@-HwP;ADn$esAE1O7FMZ#w)}e1f}yA7MBB znLHrAAQ~jT#uvhW6h}cxT8VGzW{fxv%F;u8k6SO<;s@Ms86-{-)(S7(Qjr2&!5mz_ zS%7bE&|O*)N+%}Lc*wt7QXV+~JwQ9=na%d0E@`QF>@3e!#bEboDwj?ZA%j@qB9K(L zO>3zWxEp)!>KN`RnCcYnP+5x8W6vBf_LIrb36!#1ka-yS??UU=;tSwX#nL$jZq1EE zAFRL$h7=#-<{Z{wKHs>ReICJnQ)1nMiLb^}{1l3h8)Oymd6vKZGfmO?)pq#MJ#H6n zUI;NzHC3W|*{w4lvCDjl@hc`NqaH)7vi;9RWLLLWpvewQC=gnzAbx^qf^y4Y!| z^PQF&?SsyD2ExOfMOqEecCM3@CqVTKfV()mI9<+oRxvOmoI$LVGstnuaYCzOzYCeM z4mspnu8Q7q;dTqQY>G38RUep{_Dn}V=oEVz z^*l_6Ze1#D>O=C6Lo1T)SE07E?Fpz?EYx`DD5&A~D9py`_7EtW?X)@PY$t8UZAWZJ zoWpDfp!Q(S0=`Dp! z<@Xj#*e8w61om0MZ!%WsR1`Cr;kKNLI1nk;P#nC6pqw{&X{~yLOx)EbpS5hJnx%03 zQ=I21_8-7zd(HuWj7Qof4AWX5C38`@>q~Gi_J5hh!bIxFR7@Nn~yoCKPVK{~3(|Rc73^vbapYs{6Vz`Ro z{%j5>Q` z<9^C}+}+sID!CWCTL=DHL%ZAW=`S?$2fCY$?{T$OZLre?_lccu2h~BICXNLA-A=fR zEwI-}1GfTx6zBW>!J|jxmh}+Crd@A)-t{K%t~ZJQRg=tntQ7p6W+FJ6a_rW+5C?XJ^v6`#65j3zgv|%pQAJ}2!ESDT+dIC?t2OWE!d5o+3nrx-S5W_%sk97l=eRL z8dC^!j@Upwc6q;To~PyZBHuF~v`x>iGs8?Ds|~tNd#Go;KSCey9_APl|BqwM4|@Yg*TdcKD*!QqdY;)w&-4(_W^QA8*DTR}7d^7V`r1B~+zO4W@6W`0HZy(0s8$zL?EwvG+EeQs`_J+205TW?TSR&w# zMn}Ld!zJwaUPTgCon#{!a3`Y^V0^nm1mo)z_#*)NBuhlm+9ui?m*SfZScgP%K>Hos z{#b5*8*YCjw?Bg0k1ye){n6b1aBjcxo9(AIJGsrF+~yc=v+c|_b32{f&M1syh-jtT zNi7WKwgv09nXRJs1aW&pxIJOq9tXF_hLty#&k!8=y3PQW9U{pgNRpuL&*QsQVaO{9 zJ^dpnxl}jM*9y@loIR_jy*prT!pg3UaFJ+CAzw(YV3o#c$4Go z|D+)aeUNt_o{5;{pcp4e0ot$U&35m0?+fI@Jl*Gg9rNWEz=zCGK%eV)Ob0M?nO68_ z+rg&m5XJSr;7j|16;J1(=dkx6%Gzm`0oweP_a{#0qe5Qdd412BGyhDQ>YwylTPx znoIf4`yN`k3ndYCJOS!R^qu;iN5Eg4S(?>OP)w7K|L*GN{h8K4tc0jPLwn5pP!`eG zkGYinXy(N3?~N-LdZ zV+_DQ_;E|;+QJHBr9kY1-b2L0QN7TUhrO>*nD-m3FHeJ)dx>2;nc5#jzaH1UOnqZw z>IROn)sLsl|NSm!2)v-R3;kw3K70%1TJQ>me%)tsf!q^5Jh9Rs{TV&U5{v!_O(4dP zZ@pijh1;8_^zMQ0R_{r~`LFk5?%U73pO~?l@y|L>e``?>z1Euj0rwtX!I37W!H?ER zo4;JfD#ENI;JEft3mG<7QeX0>fL>>tdbZ>ut|Q0_@YRZVh$Gaa7{jCIoR^i2@N*uP zm%lZZ&zUxrd8Y6G%ExM(FF&;0U!EVL@aELTm!=tj8n=LJW+d;I;OpM^{@eQzX2(|K ziCqlhf6FO|@7e$i+U|YH`zEOTam0KQSj_E*4em|nqIrDp3Z&k3HZBfWNmTSl=r5Sk zV6M`GI=o}?a;U?*7*oP0%=M=2*0B$0`wP?N^v&97<%X~m-fgJaVXhnaY^ExNsYv1d z)x>?YXE*bIGu3@i54}XIGM>`R^!@Ja^K=`Jl zfbVATHdMzWytfGQu3^_#BEsrhV2Q=y6CGe`1Z+gVBUf&m;=5dSz=y$Zzd`v26xwM}i zMStHTeY3p{VGr`B6=^&8E5cpWp2pwLDUsXzHv7E^EZgJ#FMMdN#0=)t<}Bs9G;bgG z7+S#bHfh_3T^0v~d3qROPG3#K@J20>_t8<*UW~mVLNN#QOf>UpZU)fhux4Q%@Sy>Z zAHM@yu>tUAGZJxPz$dOZ@e_eh^H~@e_FFKpWDhtHJm|?iMB5lX;GONA?7hspk-6G! zz}I641(yTfOpmN-dzT7v`WN8-S~Cwak=IhB=Uh!U`(4H+Vd5`DKM8+eA~$OdB1!@m z;pqwH0Kegxb)@CKwR+03k&4fdxD;K-fYQHXN^la~X0R93$=vT!8jin@{{aO)i+h&7 zr~dnopVn}C&f^RWKr5&%-!Og>Eu^u-W6csk)B`kgK)*SbAOFm#6kFq{&qJs8yPHBN z?>LXz5sOPf9+*Wi&!AssO0y4|=0f!$<@Ysv7k)HGM0b!I>Q6Zbaox|Tr_S9=He!Oy z|fV6N#HXy0a&gJ@c=kM_}j zZGQK$#;~PGC=I7YErpz$pLb95cxTUz*X(zir~6;KTbR#(ViuoMti5=wz<5pJv(j=?Cg};JdI}QhPvBoD(;yLDjk`8g zkV86#zi=Fvzu8rjlv*(WZ=&b?eO=A1W5izK}IN*{;~&2qXhobgA39| zCHO1E5Zt55V2Pqa{IB6++@P6=KQa{Ke+;uBb5xK2Fr1J7FkFFqHdjJb;63V1kebFD zVZ=Cy_rP`Z<`=h(@#Ys2MRhQB`Pjwmo4r5oB(>qUfCEj@^sH^CGuDTX) zC_Aeq_}4(Hx{=;~$ir!NpB1MUQ6 z@XZ2}*UAD+|5lZ_l{5hGK-@Kuc%L~M>Bflj@fPv|C<$5f@sRMk5a0NhVlwXW7-F87 z4|oBTfj?tj4tNp%Qe@!U|F}QH66ukAXCVlGu6O|7e|Qio8ejT<3_H(tkPd4L>97rm zNjDlo#j9cq($c*Khu8{b2)f-M@gJqP0n;4^$&&2`-*gDVxAi|po}WOqDfmB(!as$lBNfTgNAcYXC;s}fL_#72Q*pd>6xpKd!t5pY8awm1Jpr#WymX z_^;-t$Or#8MLwV54=OhNLFE8qekl*3Er%f~8pVGBOyXM`4tW%ROp)}z6x?9?UVbke ze2+uoUnwU=wERi_B)ZF=P**^L0aOMiNvpJHX_ZWt z*2raPjU1NNNM~scH%n`@XK9UWme%Oa(i#~otSaKzc zB|2KM>@au}2o|6`@wDnvJfK{vj%hx7gTHjg>Ag%V$tuObKh&k8Wz zbF<^WRd1mbqOB%Xl}uHGn5wo9psEs7_5Ts~CSYwHSKqki3M7m%yTI%S0RqG>gaC?hE$zFfmba}8_u zXxJ|_fv_K3(5IwlLsfcK&-H8&*Rz3K&q`!pXo6s0;E0?@&ti&4%(D+h4r$vEu5ClP zwwND9L5U?~{-kxu2sBHkHqvxVk0Kr=?*q(KPYVdd7s64%1e zzRf^e#rOg?*)B z$q5}-3N6ee(!U01-dg-!2mNb+{%yqH%~CTo7irrtu5JCeo;5(v!nWXN2pQL*jO$Q| z>rfv$O^c^1q!lPjI@G|mryuO6b*P>6CO@HzJR7Br$g>H0Q)o=aHKw0OW9~qnUC@=% z6}mE!>&jHFD;-={mT+BJz;&gS>&haoEA3oY!rnq;Un19)iCkAka$OnEb!8-v=F8w( zGM8(~Vy-1kTuY{LEg8wRWIWfBkvzUFgX_n1t{>C5eoW{3(Z(a*3b}raCnj*6n8S5q9@mMfTqjQFI?)7u_zK_uSdKd}{;s;s!+WpC z6@U4p`TpG}-t!agv>=Z^#Ej;zIR*daX{6hc z?m)T|=`N%wNeOMpwM>>FX5b4nPOA_9*EX5$j<2!O-w%d<%0O=so zA?N@dQVdc&zWcFN9S8Z5x88KXA zNHWs+__Iud#E>K;Es_tCFOm*PkK~6m4apyAI#K}A45UD$Af#ZV5TsC~Fr;uK15yN1 zBvKSoG*S#wEK(d&JW>KuB2o=fEm9p)JyHYGETl%HCZuMh7Nl0BHl%i>4x~<`E~ME= z-AHqg<|6eV%|q%%T8Oj^sSjy6Qa{oF(h8&pk$#Bu5Yod)XOSL3dKBp~q{oq-K>88V zlSt=~&LdqwdJ5@jq#q;w1nC*1OGv*&dJ*YYNG~D%8tMNay^8c2(w~t&K>7>PhvUym zcBC?-a-<5RDkQX7LR%%YPg;bu7-qXedFT~ zzc7f9_aM}ezc7f9_aM}ezc7f9_ zaM}ezc7f9_aM}ex0tjFRp>&N3>$5Zf%okqGH z=?J%MQ&4c?|qehoc596=h*#t>sjiZR3(W@eFEW@l;A3^|<@O@jkAG*sC%|59fd=CMB-d(>>wg>*OI=}Xa2Yy0x+Dn%TetZJ_Z=U>T zHMfiWoL)B`FTLMZ?h$u6qE~y_gYQu_9CnYpA0L}g|5YCNVZ6JN_aoIWUG%_tf4SSO z;>S^}&piFEz2B4ncDzT^3*X{_pN8*D;7`X}QoZ=kus>;t-;glrVeNU!f0_7m627u0 zJoUQ!L$5uBar_8m0`j5#C1eVp8=2AI`LP-8)({g5W4ok$`R6Q8{tc^@i{(GtKF*e3 zlr9ck*zokwed}4p*!@6&6|V-qhl?0vQCQ28jORxf!gV^mIf6b-W?a{6D@Zk&Z0u?iw!9Z?yZZ@5n zv_OwNEWJt*YPCtJsWx2JZ_SU0)TgGJl5{#lctiy7+am)dwrS~+*_}t0A3PR$aK@ZQ zXHRZkPf6R{fP;bUdxPiQvuXI=`3JV^>)IkQ}Sa6{^ZT;x0#R;Nw={dsGeQ<5s{Q!(CKf zx`-13ei-Y4so+Q8Mdxs$i}$C<&*}2CoAj%IAH@p7i-*RufZq<^wiiz0S-?+Y{u21p zu?D8`EZ}EYKbNG*{dx+sFO6H0upeV2m^GS8W4r{hFSKEV@o}IxmjOT{FHkZiWlJ`j zQL2!)_~$m~IeM)H^GlM;@`4XVbZ?ur{MPP*`8(>%H}w|abais&Tub5V)|mG7j%b^C zT5Vrp&za4=*DlV@?mgIRqwz=7SRg5oBaCr_(H>&vzuVbc@{;;`cB4F84_TQdKjqlq zknd9HJOesYCydz1L$=UvvKd26W*WKc`)_G!JF>EOXJgR8=`Afab1fP3YHZD!(pi>0 z+b^drezK|H$31Yp1nR*Rk^gQ+XM@~$NO}bP2%Dk8L);bc)0&_1@lExHCDprW@O0sqlJ2ue1XejIoBWe{UcbB_N#dR5tK&dU0fPb zkLDHBqvhHQxXB2i{NpZd3CB`J`ElP+s9UO;fol;C*B--}Iq-pEH(3tHFW|?qb|v`d zfERGRDd304U*P@S2CTnV!H=MSiJ$GblK}L950qc0y(^cvM)!}uqN?>M_8cj98Q>rC zxmRGn9e!eh!(w7aEpb!8PmjOB;c4UPny*yY&tN??PCJpzuA=?a_|JLYkiDLUzJX?^ zIm>M4lavjNASUOwY;dr>QmMVDY_x2BaP`(^eQRr53;U&P@7!KRR8#rYoa$E4Nfz7z zP6fgD@%jY(I5W9F#bS=Wje9ZWAF~?yl03|8^WQ`r zBqxVCt`1(u3A~+K^rN0|p$7!~ZVgVvDRN~(9Xi2pD1r9TdUhMQzHHWzg3rWho-Jq= z{N?qa9PkPFZLCws!?+&%KVJCVn&&wjcCHGp(?0P&tzJN_mgXz2g{)y=B+_txg|wf2 z{`s?`kMDXMbi660LYhC7BiSc1-6TjYJc%-hlg9kYXc(}N_S;zg=t&upGWMwyF;*h! z$G%ahKgp@@ReC&L^akPJy(Hi#G*5YY3AoWJ0lyo9L!9Gvs`8%#fB0-azD%LTpr$60 z8%H)PP5o}l9$yw4ySo#YVR{KXy(A`P{n|QooW%9nZ=qL$u(zwWh`8}(ra3{G638@S zyI0-T(Q(_V)wgzZ+`77QeqP@E%JKzyc?*K)o!PkY-rnAOH*P#LZ>V%*N5`hJvP~Tw z8%wDbq`Q90dt-^x8?n6A0!}uOfFD=s*x&LQTENdLy=1`*`di)`1Sg9;4xgk$yt-Lk z?h#Kps-G-!f$vc@oX_k6{+Q;1iZ0U40)80#ASBDYey+;}oYy}^`;NjZD)LjkG=q!$ zw_}IH3!mqK-^rd-!O0S!{AMkO^YQBLFKWNLocHJ6TMW(byhUQRn- zf3#)g4^?k!Y}{N^v$+vQ0F4M5b8EQvLwt~D9bz(tz^E2WkaiZf`}&G4T{mA>RbO9q zopf>AVEu|hX#1g(;-X?Vj#l6Z(O`UT^(?>#kaP}H}skyZEn#6 zw>zq;D~?MS3zk&oHD~+!msR}wsf;xFOSY%TT2Q2Dw;fzB?0~#uUef%N%Mo#42OsAG zegb=!?zsdMLB0h1Zg`VuF0nzstUyoYy5~p5nuX>v!*&%*JQ$4JN+LZL8831BXhF}& zEZk3*UYnQUNQiEn)ian|Hr(F5rN(csZ%TPqQDsbB`Qi<^RojBg);IcPR~QZH@i7)t zNVv77q-?RRbg))uE-_j&;!mFn4wOCV@Bc^-cAQ2(Jofes%5jy9tm5~5YoPDBP*7_ z_P_aX#kSeIcb7M`R4@PUX`dF_*?a4^ZTWoG;PTo&+fYfqt!VM5We$f7q>R>g5^O!P z?KXzM;5L{-{`|U>_nOo_G&J@g(X8Y^TlS&tST%m-fpU%#RIfdOJ#6CWbgX`X&y)L} z?L0T;axYY`y&IZ=YOe&JNh0BC;A?j>fM~hywuBbWxw>|=y|J-lwC<|ls_k9ul3di( z+1W&&UE8ZrAIHEsbFIRe%bX4YC*PESe_!$02sm*-z>j0^oah#Cnjr-IFm`?fe1ItQ z!0*z~xh!{n((EGtQDt=?@X!n);J0H(+>KvodI7(ay{>}0@sNfFoR1uLJKQq_mGeGV z(;+nY#OG>y#EdoZ`9!+h{o%!bO0o7td%W}=qjvHcVz!6=9o!xk?K-VFqrfc_`t6KH z)<~Xyd&1K{ClO)b?jNBc1Rh>LBm|Pd{hP2Vmol>J7z>xj54~{lkSs&3{z|ezwIW{% z?7ycVNo4XnM0#{x9`3B+GlRswF?D4mqK|eVIRuTpof>^@^f0q4-;0d zfK$H+_;F_8bP71hiGUwwDJnSilYk#(HRvZs>kI523(Gr9YmKzSH4Cz4eDSy2?z!{U z&%U_z%o(;Edkf?89r6jLmB%y;et0oo`O%oVKd}Ketm9I8iU)$2IGSN(D~T3;1EpDglQbh8fZWzYBH%r2Go}N8w@j;vp#) zcy1T=+7tovnpUs zY+hBjGE#nr&5-}W_7vg;sMJoH6R+Xzd|YYg-+6lkoZ2biCva;zwUa0V1s?d_xL=qg z--2H>M?g<+v|$Oub-2}F**45p3~w7^9}Er2vAkz_PF~|VrN+G+vw%~L0)8C!0o5qr z)UyJ97#7@A@FR-<4%ReL;>mv(;y$URlj}XIDOTYj>r&u9jU9Q8e>|Q2Ooiu+=GQ7b zUOG>~l5zXJ$e(1IXk7~H!0iF{`cSq^b;f=G!H=!PuOeG z?ngPs;pY&s;3>!ZQowmRr5z9R{LwuBc{ZKrZ$~|9U+V=#(ux(C+ILI!BrElZl>$j^ zA;%}+=RH1B;N$fRILGJdN5W%Q>gVmJ9Wo~H2>;5t@n3Md$gA@Qj$gpfV;7Cu2YM%X zbuL)A#Gw`Hb>#)b_lj1i*OgTJ5)MCy7&#$%?%9^$-u04uJtk&DDko;CbBY~XiTc!5 z!UfIOik0BTH*xlL&)#A-A$(%iILDHS4n>a=T-OcE9$ljm6w$AY=pr_U=}nsd@G zlvcZZdN`c-65vmxHzkdE{7dY6CSlIa(`e=<(N>M!jvb&R>TK|vw9m_TDe>~ud{`%Ft&(zTY9^njMy+SU+L zaj_*$*FAsZ>Yo{c7fO=BmhES<$1hBBrpL!lGsPw(QM&{^@1k9Q=2CEs+UJ4)QGx5U z-(T*5Y8QK;4=T_Bw8@>9_9m#U-&W>_DoixSsa~D-q2+$=TD5yT(1*Qwr4KyN6AL}G zsG!HaP;IFv@7cv7FHz{m;H7ZtGLI-_z$tCB2h*b}=sFMdu|-~5e&c~2?(x=|q(Jqe zU)-&f++-rQOr50;a#?Y-FDJ)qN` zf}_!gxUany(ijDKEJe(&B9W6sQm=;;mSMX@2%*YHA7L;UmAyrsPHW}cEbn)&%4*Ah zw4flz<~JI=bJ@@i$sSO#EGnfq@!IU9Om?nqO9i$Un>Uup`+RDPEkyx)Bd&huE1ycM zwK|}w-d36F7gtu2ouZq)U})3#?XBqxq=XVnbcEg1g`Hgs(p!rP+cL<~pz&fxkHJUBS8JYSIBz?R zU1g&+yM0S#<(BrE!D_#2{K~V-YHG@I%JtXyRSgD+HssE$&|7VUy|7qUv52p+1$L4Q zY*OexQ~!8S;!Ih@Ox%dt977t)#X#pk_~Z_nw;UjLTysZI;}XX>CQt zt!WE8+dbpvL(+#ze8g=y&)}BJRVw(sIFA%BBoon$uVCjS5WCvw`Yv-yq?UXi_IRy< zd7MVv=K&H0YeC>A(X1hQ$i8EF($Z_Y&GNsywv?4^>9Q|wNISHLVR~3;bJmpPnzfoI@?G8=RK9w&Z=+4YV~?wzSsOV!9Rjn)n?#X>WZh$!A&Z5be#a z9?>qIPNMxUpDGgVZ;@O@ADE@L(KlG(39jQFL!q0^()$XWxIPv1xCdIUb$Ifg9Ta&7 zz;ibS(tD66$d9*-Syko{r3~am+vvgcs0zBq1AWX>t6BQB2YPt9mqIsGuZ5&3?ICI) zuSz>PHJ)~Q+kaFus%rngJo#_eJg$O2@2U0l7MXSs$I}Tre3My-W2Mo_=hjG}wD_=u zFna_C+rU@qF1E*NpK0h>FgGM9C}2jUU(5{q`tRWBYdIN-)|q|&tHP6l0t5BBI{)Bb zP0eOcE8V<+23`zrfJdKj`Ksib)5_FuGjf$eWCF_+V;*0Gb4zp*CYkIv+OZ~8^uOpAKej~v*?-bEuFg(*-G9_S zn3d|lt}t*og7^!yt;9KC|BugjhLF0EV87hD3`a-$NM=q!88$j!&343D7dYknSy5$W zO{M%R>J3TYL92~i6Wu$04DqFW4_6A)j(Fh5v4?vS`C&0=eo1o*JV2yZj$m5k)Mzf^ z#D!K+Q#XP=#7~hMR5E|Edz9SAQcx$|mdTRIHdCO-pCd{Ffp1)e`NRBchfbR zdcMO#?qHosYm18?Y3x1L7nLLhj(y(QpkQEmyln!*v1T{4iPI#Y$8r6WkPxztfR`l1 zZiU~*tT)t+ymk6_uitbMuLhJC|JS?nrO)Oa7Ce#awA;`>2yX$+C&r)C96`(K;C}%Y z5BzS;vv|(2GlPO1dRMrxt%i#wbt8r_v$INS82d$dcpqUvc7t{p7>YRNW3W6qX27rI zm<9Yc%_Tk#Q6phi^6RvBJE@0#kUK^Nz5i{zS%h$7|2v6xme9sX5$zK>c`#s?Kp7VJ zt8SA|z_hGNbIt7OA=5IeGwkh_k*w^5%*d^2c?p&%DPphAQfh3e&M8mI>8-+GDXgkW zEVallv;3+${)f`HVW>A!U2lY_h73-s#y7|>zro_@$QTJtPqLOu5yx~zHHiiDX#i$t zl^GjqS0^{%*aBnRs-cYPlw(sjMa*mks4a-RnQB{^ zHmTGW8_RCnT3NZZt&I%kHpNCGa|zZrY;eqJD;IQiE+k8t+D6uG0q-Hw2DHECwUG{%zHN0hv@9#m6KJg3}xE$ko8MbT2yQUVV9#{(~y;CWWycd~jF+>M7kH-PgV z`l<4KK=PLJK3CIG?&(?gbF5D{xqGFY>rBySZtS;f?ork5?Njj_c20-8w3lnN%f468 z25*n|IgN9|uG600BA|ZBYb{obAP_Izd5Zo&p%*KNp*FJxhNsN_?Yy=VV$G2 zc~48rp61vBlfV4U-xqdvE;P56l(d?a+RC8`Ln+=DwW$`MsLX6YRTF|w>P8Lq)|M3o z1uI%|8zMJn+GAqunVCh=(M89!3;vRjBx*{EfAQ;_*39hIoZOadODnd7X?zVT_Ac60 zi{Ld1YE;GkVwc5R7Zyg6c_hF;uQsi@-d+0#b)rqwIz__|V_xDGr07F#M3Qpb<~u#0x+E_@y)Z_KI3l&wuA6C1mtPXvj16;% zf&v0pkL@H*L4&dFeDn$EiAaLttvtvZ0bUE}-66=vc`bo^nG-Eg&n7sq?6l4$={p0mI}3Cf*>MS>Gef5Zrk7lDmQTPxm3cYo1rDpNsG7aqvbJQPr)I7$M9S+dTsXMSYBl9Vt+ray^XkiG z3_^*le0HOBSgaG4s^)Pma;GO9iFgJ|KDOkrmd9*nmgqtxoq<$6^=-1;rzqwZQ?s0jn3Y z9{Eyrd2JQ5j(vLAx49KHlJ_Z)%g8$5q*>_&E+Ya?D;I*pC(7ps0VQb=(BpkXwSbZ} zEue?FN3#z5lAun3(yXDzOxj-LKDx_`gXRo^>b19P9`ew!g3lQOekWV6g1fPh&Ig>+ z|8wQ}0HQKzuAy??=V~fec-rlLjyYqSo0=7T&X`#GcFp~&+TH!&Eq$unUE15HG-vD* zw82Brd9s5+|(b8Mp129U}`|VHOxp(F24Qp2Jz4MwuytP^0$@Wni z`;-mHC%|XmMmg&9QKdcAoDu;iN(B5k&JYlV0#0%v;DW_B2VL2qy$*Dd zt?$`yH|w*Xy>Q)*Z8x0%>2+6)uwbTp@=5s{`M;ihmRf?jgx+Bvj1fR~q1|p0jmAQM zO-*!^J}Bj<`}U9Q`(0>d$+5oRuoQ{OTiKzp$MQ3YK3K(4hebAz zw}wNhH3E8gwiilV6FBZ--MFuRGH%G#us1hJxyXGx?$7bUy)~X{qyE4;2sa5t@EBLm z&U-$y$*-|z?yyD6=C=;b8n7KS-6hN9%B+%pnK1k^i4 zZ*@)DDO#tS?q_X}{FW^u`NunTsU=3$<`Ae9^-^Els`LeR$~gFPDf6D zTXs%cUUo+=S}5wH7Cxv*%8Q&j0VnPg99&;ZZRAicDFXVS0wwRTJ1=qPXWvoghbl}Y zDFV~OUMTMq0X;F`sZ|9%?tzwau8X{96>lNYLQ+LgFD>3OW>uL-lrrQuCrJ^Q9$Vq9 zl{AEa9$qS-ZcjKtNm9^OchB&dLBNk{MpST;6@lfnvLZ{zy-g%5BKH~SOb-?=D*}E} zbEg;XX%%E;A?d1iP=J%QZf{-t2u13G7Qk6JManR&9=S!3!*{$1E<7~?4o}TE>9i`m zD-(E>{P5Jo5G_8)9jk)EQ^VW2Y{HK51-DjE*2(k~z0dcC&06LHz3*)^bbp1K<)G8QBZiG(fWdJv$CbcHKK=zbMd(u;{ZgM|W=_X#2&=lts z--ug6g+}0IP!s&%((n1`oJnm(WMu01B%?0T zQk7~74fZtz2g9QvXt@Ej;JtuuTEy~N?UQ_~Ysr$X&LvAaO(`j+fO$MFK=|-y!+Z~ zcjKrq}Az?=Z44l z7OGU?`_(jE>J+<0>z70*a z4b_hI>&wcjnp2xpq zH}9KUGvc$n#CSSE+taZ?b=2 z10?N~{3}*I_N=E1y>^3i6PLs>MG_IkkCxa$#m&HlL5LV#xF$(pOpp}#E$vYuGlR7S z>~~kcpP@@CE7%keA0y4c!NYEOXrU@_`e)_+HMxyH8{3G)qvckb64e ztLJi$dLZ^v&R+RdqEhM}dr;{wiX_zGra_W9!a=td=;#&#e8)d2k3Zk{PyES8`+ne@ zPU~de20LQ$u|&3Gv8>;;JThk5Uk}P(8A|Qb|9bGJ2}3~zCpERFW#_EMk@ohHM)7+nquFY0&Je#}ls+7zxPM3k#pR>D zSUnTBF{WQb`+Vt1tqqq_K-SQ4NYd=0r^mLBvW&fBzdLqpNd88KDEgmhe$*i%4jy zr!Q~zAm9x+HVKW=ejzi{MeS6%FP85(Bb>2UerOwt@J1hd5(_);nL2-xj4rEN(;LI^ z7Y|5kAH8|^_1Eu~|2E3c)JYdV+ciA2ldbyNSzgKaF`=Ec1>iuc1c`#IR!Pe^CrBTE z$$Np%Bax(4C(k3Vm;R0AbNucTdpA3Wcm+coy+k9ozOi>r-hxe4dWKVjk#_6ni>e&(e z8?&np-4ErImJ|$aTD^W9_M|5qzo#*&oQm_&@zZAFr0>%hIq|gLqF(7e&Rno< z{l_{xkM$q8GxETUrH!TY^77`DHuvHFlkNk0`2jX3c>cW`hVET(-7bAoXZfb~_D!Xo zvvu7&s+e;PIGzFx@gsF@%Dmv-(ote~`Ix2P8n&u;e|ONq8GX%8s>wNP*^GlT?>M-e zXn3A=2UqRr*3IrLrFzOcoAkS`TX65thI{9uvNdQcq=UzG>oiqdAHf!5{;E^`PvYKr ziey$!(xXpWx%5DKwbQ|iNZNVjqez*i3??N4QXfJt{ojn5!peJ{Z5WkhwKOW_r=ahC zEZ;>#Gka0v=Hg;8C=m?re%AnIhce-U@T;D@9$YEvfd=uydFD zzt`0Cz5f0io11U!pSO7Nyq+aXNDs5j;bHmt38$5|) z5YPS=-4hk54YS&4cO5~AuiezSy}EjPCkq{W=aJpFU-P~1RW>v*ZSL~smgV`V#}Xm` zm2LV{p`!%%x8Z!AOX~-`Nt#qHGiIB(dO$>n>M>bBd3a}bzWZ@GEOmN-DX6-!ye_rM z7Cbtd5?0<*gqXq7En`ngdBIvX#I(+;vg+`J5cvb?Vfp?fLwZcf>c*zE4yhmAhxX8@ zejEKq`U1L=dT;WeP61n<9X-1H;KAKTZ>X-RslGw}kbN$_D*toy@X*GgB8Ri&(!c%YvH$RP?85#E}+bmzl3RwaU&itwO7eu`>!=gSj zX_iV{phA_vWVPnU(?u(G1g&Gg#vMP(2f<`Nlf)7#s;Z4i6;Uk4nVeEn?u;`PM}-$X z@GVF1*^IF0u-NdeArWEGIO4i3FiigQ1bLZoDj)L)&9LvFkAuiw!2vk1k?&LLwRC3g z;HB?}W>sWajZyK_UxWm{#GbM`V}@c9$6lpA#XJHNm)ns1jI)?a%);*RSU=iDDfeCY z7IRWeEpB7d{YkQp9;Es8Aq79^n}8jhk}RqBJ*76RCL-fVlv(O^js=#tdbU$(*Ey;8 zjcrruRQHT`g+3LhEVvEu1JWJt@1PdD8e1VczV6d^jqJDbYvKgn%PUvDtUiSYs?c}R z1Dq<@cM4UWy$7vUGN;@()~7n~r?hS2u|MD`1fJh;JaB0!Jv6mVA+;1g{oZ7=LP0yF zaiLg+*8@97UzcZyW6k0n`L9gIh)L)Nim4)hEld@<7(-d3Owi9XLU8wxj}CWZE6;H+ z#ukq~ej1(x#oCkTb_y+|6a`7r+nZU^HrTtdI~Xhp7N@* zHc#PpaDtO3n7np6?zN-Y{7c+xPntOw+$TRlnPkU*YoMeL)EZPu*)zom_jTq~qsH2G zEm1N4ct=EBeT99NCB$E!+i0m-QS2OUFRrXCN-j!_D+;*vRNvmttQkK3Av1D|5~k^6 zbDL~ZYEeSTy7s1Zr7LS18yb=-vh&O1z@0j@QtXO(Z*L>5rpJ6i29#$>Plgo9kg1OO zS9P1~%bGeC_no>iH7z6K;3i9Ia>_0#BB?UdRw?;u8yt=GOA?HUDYH#+u|~)wYGJ>^ z?i7578^x--4*P}YtEGtL4mphENaTQbT`J~WIi`64pYD<)%Gw_DFs?7EV?I~QKXpnG zgD*SfP&9zz)^Wc!MPp)ZFGe)?AjGpB^837|U-kEc0A3sNM~@%FEz-0X2zz5II$tc) z(EoZp^Z}Wo6(K3{x;VD}s(sS1A-rF5FuG1o#98_aBQ@fATKaFd3nl&-?Yr~8tD3~yIV&v7oymR!U zFUHD_e(}-KcPQ4BO`~I`^1s9GytB9+R38@NL@s?D~bfQ0io!$S%N5}u`8`?aORw- z%OAfCzb%jJ5j(%iq8w|^{F&k5+6;Ddqe9e`C_6^5p`9vMUY@2RR1uopz}3o%qv6b`iy4!3P6BSS)Gt zTAN#yV?er>?*?rMH&hip!MYb6m^JIbqAkit=bSm0kUEbYE7>}K-WJCrk2to>o4>W> z7}K!I=C0YTt+Tth#~5RQcJ@IL3}li5jMXJ*gLlPlz>1xIjO>p&XU9II?#v$?T>i2& zBDb=~5VoLjj`SL4LGOAU)GF(BTK3;#2`g^QDeuW1v1Y~QoBci)-N4uC zo|4krIcG0_suU;62dBcp<%e#L^+*q`)4BA#L6!2h4jw$HjFjo9F@QXk8{w8v2DwUq z*NtpySdf*qpkdRxoV2tYXuls@S_=wVEz47qO{vPre1`T4BzT|TCr@Gj;{8v11#Q3` zM7nZg2-fLSp}(v2Ay-fR;+&kt^*!A=>FGJ$>A~89#j$O1qD0>&Fl$2EDM!Ex~YC)R@TD$O>1-0(sI{|LCQz0x+So2iDUCn3yr;S zny=9#A!bNFxHd&Cbt9c@uyJG}c4c6GJh7YHnw?_bbUez|lyalTDHC@{f=~gLItgq`v z92b9~1W<3Ts@mLET2oV6R$EIU@c8cH!F09JrdU^ zyVusoH?G6Kf5=(44DVSmG?bN<_0ZDp?nPstel+tpEac9NR5NZz*XW=H=>$qFUyFL< zUg_`xd7N;a4L7n4a2Sx9k7&=wVU(tMdC_t4bRq?}&`TG;wxrXMl!rPSaL$3{igPJa z6@mjnFQW5sU*OH(wR$EwUOBRQ&8k&vR`+LRWMuXKfelGF%NOQ1HqP%e;@?YseHv&> z(xX2?+iCS2gPR_pw>Q#4me$Y8El}?Dvl(7;6Cx35cFLX~oS(|3rRE3EAFcL}ia273 z@~=L${i+Z4{c+bH_kAEAlUiSe&6oD-eNmyIQTMTU`TZ|{M*o&95i zHG7xNh%%%bNwJ{*E?#fc8?4c>`PsQq5h+2_Ld}sgvvRBnQMva{5kUWZe^_LQAtWSt zX;2tGqyId$Ac#me#RA|_Y~B?!MZB$?Va!g<3M2Z-1Y{fnP&Q8;LwrH%zMSE#on3WqgVFb zaKpY=Mreu|VIyOo9((I8;;cJA+1O+cp$?-6prcZD_DA`*?vl$vo2slty@>Z&Q7?LF z^{kxDdiKxUN5(FwLia?Q)$x164%otRxL3^vvuD)|3RE-uSGkY;Zt~@=CWlUPB}7Audw$>PYZkRbU*cg5{-u*xOJjkNl`d>E@GbCQ6lcxky=ph zjo3g;1;u>Ox?J;bzFQ9y04;m!GeD<`zkt<-ywVS$VKIB~JMOh~zx{E%P4I56>GhdIEV6B|ylMBs{GJp2<%4aJY-mWT*-+cE!Rg%4QoErBuV%w| zA=xS!9G=#cHPxE6XLXw zv0Xp>?X_11Ym@9`0qss;Bn<=F)$!`I=R;GJ)Ya3q!I+RF$830AYMtB88s#Swll#~g zatN&uh^}n-dvE436vu3#VaMc8xrju~Qh z@FuBO`Me9+hr2{!uzE8BhoJRFgClo%X>Lh$v^BM~-fzGE_qMlgaHMqA_4L4dJ?HAx z*61`#e0)}PZH3?Zt87ck3tKBwa^@PrVZ;=pt!=0U3_(j(+a8|N2oNb%jmi>Q$DCJP z>$^`ktHfN9+FDswX00uDI-I(F)AtRxZ*a_Rox7}NSMcUOU2#EVZfZ%<3X3&6Ki919 zS(V#UTG1I8642LNv&2fhjXhZ|WB#<-SZXucXr*H?8B(xZ*@FwT+HURcmhXG@#zFS7 zTs(B+I0cC_oGNkQErl1|agbhnkT|0^!L&B2ckx+W*QN^HKEH;d%2~#;%&h95Yx3CR z@-wj^!3DKx4XYj1JA(@rmg)2J>nqdClFW(h#L)XevF7lyHMLDcWp1gcMo%S>l_8{p z4?)G;Ro5n*SZ*1KU?p?6l_u3~XkET3d{;oewRx^j_;hQtwSJAGdTX1r$yjVoFHbR+ z1~=a_Fz>dZie;VtDe0pJeG&?dC9CUOha790s?y7h#dy8tgsy8wA$lOf&jw2`G=l2}*b{6^XV-PEe|P+|x{3 z(jEsKCYLbb{t|mJRdcScU)mv6wL6E}+J>BM)l$dO`jNS{T`f(GtzET6WzLe~(z4*( zg|)3Kf(=0(mBl^z`8~yz?ZJlN6|J=kbBBwIY?YO^qGHyNlVi!xx8xuQgCb5`xIdA0 z`b59FS;fn=b55_LuGIM0Q-O}C`9=)WtcD1u=bIYQg4rA@w%8XKz(V3Cv zNK7mVp4aA=VBCI4=Wv#;nbkI2)^XdK`A1e2v-!sI%*@J^b_ z^h~1_T|YcAWOW;?VUsJ~+f$d56}{Xrsj55JH5oZo^6KxvoIn;UR&XYY+~P;rX#7O- zxt7F%?udz;TwMr@dO9LDGc&iQy0S^v?wc4F73&usl9!nod|)yOO@VPi5zuMY%IZ=d zV_bAZkRcc*c7Dyf-$7M6Zmm)J-h3rd)CFRz8yZU=eFRNhu;Gv)hFOHa-&aaeuBXGR+$A}ix&M#V%Kw8_@!A6!8y zMwOd4q_<#0^;>ONTG>o6<;`}(6{2xRkJY;HksmEt;y0=4J38wB`uB?Z-Mi%?qIGXiGU|2a4;H7-6m+~AX6d}Xt}t&YMxh9D6yMVyvMyT=5z;xfM-W z18e_sMHMP-L3`#ocsuH3iG>D*62ot@Ye-{o?VNGS~TPfMoI#pE>qz`~RQXI)fGbW&1uOw_}XGqJZC zJ~L8Yc?J1baI(_v$h6av72}HUozwP*K0iWFu}YlqD^$9FSJz%w!&0)SKFbL4#&zvZLQa?bUIgF z+gi4!GdsI;OY37wYU=lt7i->NO603Bt zWj&qUFMlDd>!Bf!eJ$*cX3VYs^F0Pdb9Yu%?V3Zscl0#W)iu=D*H5-qOX|3%-aw_%&&`L%ql3M)@?@aNa@vjfq1K8HWQp5JhtiSIT1`wf1gzOUI< zcAXE6EsgvWV1$jS`7OH!kk=JRI{LVp=Xt}OXZ&kc&mnIr5UgofBioBoaG!8IX~@&X zA#bZ7i+HJb+;~tbiP!ZmAkU!oc$DhldETQu=+$&Umayv(H;wgE*MBw+o^P0)-0Uc= z_HXxzk50DuK+MZq=2RO~Q74exEXA^a+3NVDJ2{RVqQ$kF)M+Ndd>zo|S2U$JlTGRmU|dHFTG{F_p!TMnhqrzrmx`+@tp*38SJ z$9egFUj8lij;H>VDayaCqAz2L=kKYW_f3KSJ!v|}5Bb)v2ggz+aFo}NWi{IWo)qb+ zKY~98NBQ%a;yHUpDSreb&#a8Rc_ahlCo%F8m61n~^zoCBBg#V~Pvkj*kw*|3c?9uC zo<^1c2#q{~q>UfJ$Rh}iJU3(!FGV9y;5mtrM|IK2>qV(}loL32GLrXEBCpf4z%hp@9wWx9E?~&aPwCpH$^!$h(kB8QJ84>>}e)xL>@!5Uh># zbOW8$kP)1jnHLi77aJ9q=-aMqs;sWb&CHCAc$!t`*W^b;`C~C05gljrDXp%w=3w3q ziVJMwbZ}ikw;G{!Zr2MBH{nF_Z574yLbdVb`9npOaoRZ&)-g#*$#HQ}@zHV7@zPvf zJ=QwO$$4q*%n@x()*7&KnHg6Z8DWT?8SZO!lw>ETPxH?=*(ynkC|U^~D0p0`5A4_? zgvZ5fwLSafqYJFo^GjKEN5$X&THkToZcwUdEs8h+rSA2)pbuLp29w^s5P7{v9}4F$1q#5(nj4*O zdW;387R0cs;lLGDViA4NVdNFtdrsiAiG}h$3rDq{VF+qx}!NWBQd3> zVODjrF+HoPlZ<*Y?xW&jVxnW?nBzaQw-VwsjC9Y;^_afA3l9yeL$H!9!3 z?DC5UDwTi9%v-l!#-TC%DgTmqi&DB5QHn>i^z#z5Pvh(7De~_Jb+s0Ek;_}RDy4je zuPh}!;wd$z)&6Z_DIWKM`^vAUSqf)34CY&|zaFxSGj|*c@A_Q4bPreM=N6RSQg!+_Fs7dS7+ z9>S+YJ|oS~Fjw+DuC4G7p-S#X!Dok$zu&0d-)Gw`ZznD3PkZaFwEiVYZ-Y!KH@*nv zl0~_>wAo>|!mDJ^Vt6_H{C(#7`0H=D<(9j68D3htcs&L}K%>Z%4x3Ku#u zop~`41$l1>`sjwlIH^F?5GXgW){U(us=bj^uU4LV%jl&s&qr|hh^(~S=1%` zMHOiIe6#76=0x+Gsn^*4MJe1lQ2iAD!T zfq;Jk!PZM|xOx1a(t6F!lpj1YY?%Hfs(PUFy^KwvK}Pj$Nh@uh8!?=&Ero~hts4W0qUrg@NYx|Gp^ zW^Vni(V`;s+eS^fQrWkUoew-!i*}&ya_)uKR8AT-aIWwjP8w0-El9%M!UiS|pf>lw za#zmgPZ~zv@#U?ZkE%auUO==S@ld1rj3CU4arRR&&X^VZlQQ1SbXw#Ye~srcGK=Op z_5zg}|2He-WASsgll78>QY?;{&HjAN8UdFqD!An1fooT&;MxiWK7Lf121-cMIiB}d zuU^gZ{8U@Oj&OfBSw-BRsO+uaMOd_3W`|Qv>M|ACQ&Q?pnH^cq?52ja)YIA{*7^=VAHU9qmb?sU9j9kM8xXb3HQ6HQ^|$b8LTt!I2aAzokFXkO z2N?Cuasq2kc1>xGb3Up|NI-Qv)=3$8Ee)N(+fm<|lP+zTP%E{i9jzWjtD)~|aT*7~ zlU6o}vntVd+yPB9Ft$PI8MDZP*it$g95UmMJ6QC(0qLdzs4ALu*Re&~PL9PK0-YNY z$&&v28|NEu-SWOka2Ow^aRtz45cE_r~6#T$;bK z9&HJ%2+dYbl9}cG;jdC#uRNXOIF1#|$Mm!^1ZUquaEsS({tx?j?9pEKPyYNlR<13k z{dH;)_|3}YkJ-$<<=Snpe)z$}9CL}!#t`$~y=-Rr>mPjh>g9u6pJ_f}b2tUirV0(% zrL>vMEa!Wh8^8C)CsM~9a+`%c+Q>)xDK>|F4vLUl&=iRSu?8!fBma(PN6_TiJJ_QZ zxlQDMj@5GtF~cb|ip@DYjt1Aue`JY!vvTq8m>~JyzKea!URZjW;x5hOsxIXx+dgKk3GeBn7z~rdB~H`KYB@HIf=fZdOHazNm0nnA(3>B85EV3Y z+$0Sa_Bd<7?B>I5VYAJu#g9Q>VOXo5qhD)ak3ZLm1LY9*tFNyclpw$vm8p2=eNXeE)$0m!^Q`iE?a1X3 z_OG1m>^!ssD+kpYz(h+2U+i;Bn(MI4D7;R7QG5U8Fj@rIX;I*GuNzdWjVa3L*f&qp za${l{@4BWEH85UoTGGGv_>F%3uDdp@Y~&k6IhwP)>l$w7iRln`NvkE6oMr`+*B z?h{Piwtkw~t$c%tjsTfe?fKudjooWs;Ph*n-fFeB1T_1VE;#w)=E7J*pb1}r@g*2M z5H$V(<2j_;iZ+$+cxZmruKQPPeXOrwfzw{8HTc_VlS?`*1&hjxTKq%(mWMLmsE9m! z-E{+9hn5xAA6QbdBLBHGTXcAaF~u5dsh=4!=kARw9@tskbNiavJ8K+yK1N$Y*6gC% zb>(^KKD{qST0_Glm-Z~!Q&HG=ZTI}EtHTW^)2mVv^J3E~({r2{F%;!-6nfu?NVa0M zle4_gP5~6;?cpAjNN(Ec5va?P#sIbK3@c2YiLU1BBt1wG}tGVC_=HsOy?vCfTT7)od<vfjR$4O*q$=Ds>rEUDD*!y`EEo|UzbUVw4Bj#)g);%`J(PG|Y#lyu7 zP=3dVa3RqP7gP+4V0lgJ%6v!l&VuBO@Y2@0?o`GyGBVSPR@7H6FVm0co!K$w$XrLu zvK(`IMrzT3-IAZ~Oo&RK8C6`F-BGB|$PJ1MjY$g%%qUGaJHx|^O!*DwjJ6_OdRB03 zXlz1YkfkEmToe-Su;kRGvg*`?SW|Fle0W$2rVu(G{H1mV+C;mQyj2r}UM4FXD5|Mi zNU{m`B1q=sqz&Pg;{KM*%$ELQ<+HS;*J|x8v9{;?Ec4AV#b-xFWyjAn&(tpSv33Sv zd)OUF(?W=PGFt{p90Sdnnau-^l7W`Y=7NQ#&IJW&_1V73neq0NRC|IUHO4omF#~g% zMRPCSu0(Ay;XMx}*s;H2$92N!i7+d39SJ2lNR_#bRh9I&SS&65C649vx!h5{q^M{~ zdFisEqGh(B;_8I>x>jcjOGnsARQkOZ(C97z8ZBjxbO>(;w=^md6>WmI z!yRQwmH_=oHTxU4rERYrl}+70W~Qw6uThvS#tW}2$XwkY5PTwEX$5UFJM^ zoF7-^!is_N@o08WUU`nl|G>Zi)1e4iW%z|_qELDUJm)Dqa?*g_XDDo9At|aURo!RO zKUv1*yxUm*=t-P*K0oO;tOW}|6`jv3p$mf^xbkW&vP{3kEoHBwUMR}B;5a7l$>Uv2 zOf=a4Qr=k3zV+Nsfb{-Yd@sEUV0RrNlXd*9Sqtq&x^lcV3--PRHhhz`8d{i6a+vWw zhem?8K1CuXs7u-r@cVC^*)EA&nbQ<6`M@ON%)^s{6z8oVwSeAX#f7AeZ}&? z{^I>td$J+(0+ChxzPV_B@xFb<`&oLiD8P078EL*Y5j{}=U5{?3p1@nIxQ~JqI268D z)$=nT=oru2yv?l%bN1BhuGXd1kg=_Hw zonGyZ%llhO*Ur-8g%XCexY+chU_*XWap^*-A2pb)P(X%IOhGu7H&mO9&+0YVnU`CS_FZxPrEiP=H zerQI^uxw|?r_00a;ecR!-+_*fgUjvC)eU;*tm@58jhm}FD)mjn<^SEtlTjzra$tDSlw^@f_D}XyKb9?JUljbQ^54%!kp6Z{WJIVwQX=r z(>djro7OpXko$S^THUnL-0b?0??oJDSGNX4hosx{vS;+Jw9YRJP4I7(H|ETBgv9u_ zw)#f~rsi2v{N~VF20K{NQy4YkrVi*Aj5Xm#G~wPQMY+>yXkVmt|Dfw~hE~_q*Vn9m zMLIdQoZg;0vVHs2zh|Y@EzQ;2>*_Yv)zKW82D}u*PdA)nf0sOCz9BetL;|XQg>D)< zB)`YPc3)qEgD*`W1!a8a7l?TmjY^(FwPVwua+{}6BL3j z!tVuYMhb;1ivju5H?m6;5=yh*c(-L+UFFs;C(bEVNQGlBN`*{X6uJ%U`PkPekCFm*NuFx7UvSgSFPR zuStx3NixYrNwHGa*l%k)q>CN3@1I&w9lR<*jD$CV z2eKCyJ0)p&X<$gn%IfA7|F^X3fsV4e@^jyp$xQyvOeUEOGs$EoGm|fw{Lv%~lm9{> zA^%M>MnXa`$&dtNPyty$2?%vT7psM~AfTQje-sdOi?w!-h^`h_>29$-)|IVwD=M{J zq?BV@>meWed+(bGLxRV4cR0hBH{X5t-S_Xl`|i8k9G8VBqvHBwZZ3muQ<#d|_DPbAvKAnXF%_#RUu zUTj>j;IRb@l;2K?dXcLsf@eZ1`T_iXH5!BCI5(=iPv?sG<=Q8Nt}<71o-Wb?K#6j4 z>8BRB7I=bb2;+ia6#=1Kn+p5-0Lw||n7{JcmAHn|@WH=ouqu=q^*2nH# zH{-tWJjHrkAE~J*C)~TaF!(q-ipUAS=>{hhYOtgaG+nS} zrkS-x1)WQ5nRa7QK`&-Fw@sEW%(Ym|ScBel=;DVIe%(gxbip5mQg}>61)Xm=CHOtehO3*jA~$SC8DtdbO%`w(9@$}djUF0Puz{d!YkNK!tz%|uSX!C#f-o;jpt$S(* z`@^zj6DIbD2@_y3_ZU-QSNgG;#EO&-?;88Qee_#p&w&FpwEWVg<^0RX689o|4$$FQ z!(|~6Tp!uqIlA`Pk#DWta4*TY?d9@*x}c=6OQWNJ#mN|=cDxIEG9k}shG5+(Zob4s z4~tO_iL>^oZ9Jp~deSlfzD3sMJG;G?=8u$>g=!00YKqdUmo?P9UT%~2jNekD_S?p@8$tiQR&8utWUa~T5mf~S?!EI}}H3Nh# z$k~%TCt6;lulZdIecapiFv6bS+dJQlPqxQHuZtggdLlnSVBnTwbZ2+?)8H>H#esOK zU#Xq;6I`O;_Jr{b>S8uWQCF#%uW9~2Q276$oSv9cx68->j;b_VzjELv?V2T;v#eiO z8!QQ18~hR@5BgrZMe_66zd-1D4@B#C`8bY-5I(c-J^wslCN;o(USK@2U1DC~WUZ0; z;~01w+oimvoD%BBt>Y9kX+ohaXB#Fqid-G6m90YA$Tb8faji*>f4OY8T>) zxFphHED7u>0{agMQMnsgwo=tiKbcyqIcbo1NWvZm^@5QJ&8i+Br`IEDT2_fFO)Y0g z=R)ujR7qy_gGvJp((R~3_YVg}LG&6=h&<6X5?Wv{{3_}5XygT|-WG+598oQ|S~hvp zU8qdp5j0y!X{1+ycwx^V{g?)o9jHsUy0b5c(S+Mg_$YGSv1q&W$s$?JlvaGuad*H23OW19&DRvU4c7Dl!igzHtGEMi_AF8yQ+YW&P|n*T55W9Vk6t|!qzYe z6DQevwMCDxKKc+m5;8R3gN09K)m!+~MURW^0XaY`Eyc24Z?>km;s^C5b?*G?cx}2R z#cRIB5q$Mezd4dxDChdJGK=cuikYswY*(?ry5c)aIZw^kJi(STH`-QsXb%AyhHwBZ zmF|A$v~v8GO?3AQN1sz3rOnE{+e4x2i5Xc2|1H za_-Q<`=35|zJepH3^@bYC}D8*p=S;r6qtDg>J{i)ta$@C=Y)hH2bdPocRp11{MQ-! zr(wlN`WIiE3cC^^a?@N|bNaSnK*D^S-lvPhn`l&pyhX&d7FdHE5c5R04YEfz4WB_Y zI^`dR>7w!_(1!7jv6Ctd;1r-iC%fnoL|Hyre3W{6nSO9$NAEi-Y!v1>h&|zVd10ul z9RlXH*yx-AZsYJKWrx6w6%-O$bNr#L&}%kaNNU7tm~x@;95As4)yyL}!(_$;kP0c!aScoV~L zWF~L`GF?UGAieSdi&sXvY1cG*hjcOzuS9!aKE8h4x-ZUtL~p>^Ta+G+RSQilOd4-i zKCsiyZe_ST(@vgqpc5R0*X$@L^@J&q16t0sl2lwrUp_t#RwbZb0iFM*LIowQ8H}o4 zMKjz@`^aNgZU>3L$hF^lq+*sL)&uymx%}za5>w(wXQ}q=N6PU%9O?0wxn*enm0#07 z_OXD}$*4*IbC32NKfZoFz9%9%1mqVIw^Kt;U)4s0-6r?9KQvNaKJrj|_?Z|8ZEo+_ z5(;oNL;L1XfYT2xw3=U@j$x_}nxUGjbc* zbR6M}r#HtRfQ<>kB!qKFk|U4EQ{|hpB1!%P&@67sK{z*n(FZJz zqa&j%!IHEy-mGtlP1YYXdrXdGyFvL^eWw&dPD4hrF~y_&dq=FHMH@%YXC>Pb3Jor0 zv@X?N5uegjWN?|Xl4sDLnzee?5v%k9+MJw09a2n(Uio8Eh5=FeO4I)@P0B!`?p5KRMhWrg7-2D0B!{dhz?KpI3{P1B|`PM2Y5bHkt#7M-R zP2#8bFb7ykWCM5EEksYQ@4X^f1dqU8f!f)d#NJjO=QAQIPtZj+sZ<%Bo~MpT;;Pd( ziH2}bl<;g1mjt;EWnq&diklcGn6E(G9iu1t+`L*Ym;X$p^?FhG#BQm@#rB z8yox6x&`_rFq zjtB_xUj9e^M*QBP`4pZ8(3b?U2yX1zSj{>RkDk-fIr#?bu3Wvbhjl*uFt?CkowS9= zw?kw*uAASX*(-GOC3Fn-vV8Z&_rp?2=E_-g4547ptJo;SJ`sE60HXY0uPiUVe{o-{ zwUu>l-;RBlC~*=-4Fu$tIY8gy``IUV+_62w0Sb@NCm|~%?~6SUyFm>P6q>{s?G0a!NH+kVI@dsG+(kq z{9F|yKjOU8M8dGUK#EvHFI3I#X@Uu=-OBGi_Bk{6b6MCh*-?{)6{6EY8y8 zmAh4YSs8Z|{ou;aczI##ZrSf%Q}x&0v4QX{P~v?|EbfF<@nldYw7)pmgiS2x&MFZ? z+EXRJ{UtR?)o~eCtId}0$-dc9QsfMLQslO!rlzK(q`S-WrI|UdLMVATRe7*2vhWB= zz=r*E0G4jiI_&Kw<=H8&E~n98Oh}rcwj(h z!YVPE(I;cnLLMF+i~A{j#olGNMc$;c*XXOEA^s+poHQFs4n6~*E6arn)7ibIo4h%0e#W_o%$li7;uyJIG&%{tDc)dWwd5b3 z+aa~a$e8z1o75puVsd<3YF0&UQWBodin%|EP@=YT1N~B}1{cEmgmxBuD8RYf;_=0c z+Z=5N{Ht$f4O_O{cH2@gLD8-a^wP8(Xx~VV_ikS8pO#~f)PgdwlbI}I@}y;Q>Q0uq z-;|ncO1A3tE=!3o!=NHtV)hsklVjLyN%DHK@?qx3N6CB&Np}J>@1Y3wU)zFm|GNIZ zc1Qc#RF^3+B_Ua~T_M^oF(poIJ2s!!^sw4wRkPJ5tE^Q=zYTiyjgc`vMh}opo!_~* z?Rgy1_2d}uqS(a;`@&;<8{|pO5uiWFWzz7(gppr>p;F&RGp4OkBc74)8pZ3^g%EPr zxh<$PN^i6@(*q|fkLTI&lXbZrnQnKc!yS2o5@5n=$1z>Q??)Y8uOrjzy_$!^!sc@S zm7jB1m1C%!bvVoE%);kMdxkwT6aT4o=%VCrqHqzS0@!BQ?L#*8!@8UhlinRp2MOyZ-tP&g z#oCB8gen0q);FY|2&cu$gfzq={vNmef?FefAe-uekqdXJ%RL(1-B2- zYuuJpj2_4c0moAOLb1wMz#X;*0BnVT%>(TB0jotX!lE-xS7sPXrq^EV=-~V0hJ

e~bN{1`E^Ghc%hM!$b&bgA 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 _buildAllVariants() { + final List sections = []; + + for (final style in NextButtonStyle.values) { + sections.add( + Padding( + padding: const EdgeInsets.only(top: NextSpacing.xl, bottom: NextSpacing.sm), + child: Text( + 'Style: ${style.name.toUpperCase()}', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + ); + + for (final size in NextButtonSize.values) { + sections.add( + Padding( + padding: const EdgeInsets.only(top: NextSpacing.md, bottom: NextSpacing.sm), + child: Text( + 'Size: ${size.name}', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 14, + ), + ), + ), + ); + + sections.add( + Column( + children: [ + NextButton( + text: 'Normal ${style.name} ${size.name}', + style: style, + size: size, + onTap: () {}, + ), + const SizedBox(height: NextSpacing.sm), + NextButton( + text: 'Loading ${style.name} ${size.name}', + style: style, + size: size, + loading: true, + onTap: () {}, + ), + const SizedBox(height: NextSpacing.sm), + NextButton( + text: 'Disabled ${style.name} ${size.name}', + style: style, + size: size, + disabled: true, + onTap: () {}, + ), + const SizedBox(height: NextSpacing.sm), + NextButton( + text: 'With Icon ${style.name} ${size.name}', + style: style, + size: size, + icon: const Icon(Icons.star, color: Colors.white, size: 18), + onTap: () {}, + ), + ], + ), + ); + } + } + + return sections; + } +} diff --git a/client/lib/open/widgets/navigation/dg_drawer.dart b/client/lib/open/widgets/navigation/dg_drawer.dart index c7cd98ee..bb6ebcf7 100644 --- a/client/lib/open/widgets/navigation/dg_drawer.dart +++ b/client/lib/open/widgets/navigation/dg_drawer.dart @@ -92,6 +92,10 @@ class DgDrawer extends HookConsumerWidget { } }, ), + _DrawerItemData( + label: "Testing Buttons", + route: const ButtonsTestingScreenRoute(), + ), ] .where( (item) => diff --git a/client/lib/open/widgets/next/next_button.dart b/client/lib/open/widgets/next/next_button.dart new file mode 100644 index 00000000..e5deec9c --- /dev/null +++ b/client/lib/open/widgets/next/next_button.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:mobile/theme/next/color.dart'; +import 'package:mobile/theme/next/spacing.dart'; +import 'package:mobile/theme/next/text.dart'; + +enum NextButtonSize { primary, big } + +enum NextButtonStyle { primary, secondary, critical, outlined } + +class NextButton extends StatelessWidget { + final String text; + final bool loading; + final Widget? icon; + final VoidCallback? onTap; + final NextButtonStyle style; + final NextButtonSize size; + final bool disabled; + final double? width; + + // Internal properties + final Color backgroundColor; + final TextStyle textStyle; + final double height; + final BorderRadius borderRadius; + final double padding; + final Border? border; + final double spacing; + + const NextButton._({ + super.key, + required this.text, + required this.style, + required this.size, + required this.backgroundColor, + required this.textStyle, + required this.height, + required this.borderRadius, + required this.padding, + required this.spacing, + this.border, + this.onTap, + this.loading = false, + this.disabled = false, + this.icon, + this.width, + }); + + factory NextButton({ + required String text, + Key? key, + NextButtonStyle style = NextButtonStyle.primary, + NextButtonSize size = NextButtonSize.big, + VoidCallback? onTap, + bool loading = false, + bool disabled = false, + Widget? icon, + double? width, + double? height, + }) { + double heightInner; + BorderRadius borderRadiusInner; + Color backgroundColorInner; + TextStyle textStyleInner; + double paddingInner; + Border? borderInner; + double spacingInner = 8; + + // Size variants + switch (size) { + case NextButtonSize.big: + heightInner = 44; + borderRadiusInner = BorderRadius.circular(100); + paddingInner = NextSpacing.lg; + textStyleInner = NextText.buttonLabelBig; + break; + case NextButtonSize.primary: + heightInner = 36; + borderRadiusInner = BorderRadius.circular(8); + paddingInner = NextSpacing.lg; + textStyleInner = NextText.buttonLabelPrimary; + break; + } + + textStyleInner = textStyleInner.copyWith(color: NextColor.fgWhite100); + + // Style variants (minimal styling for now as requested) + switch (style) { + case NextButtonStyle.primary: + backgroundColorInner = NextColor.bgWhite10; + break; + case NextButtonStyle.secondary: + backgroundColorInner = Colors.grey.shade200; + break; + case NextButtonStyle.critical: + backgroundColorInner = Colors.red; + break; + case NextButtonStyle.outlined: + backgroundColorInner = Colors.transparent; + borderInner = Border.all(color: NextColor.bgWhite5, width: 1); + break; + } + + if (disabled || loading) { + switch (style) { + case NextButtonStyle.primary: + backgroundColorInner = NextColor.bgWhite10; + textStyleInner = textStyleInner.copyWith(color: NextColor.fgWhite40); + break; + case NextButtonStyle.secondary: + backgroundColorInner = NextColor.bgWhite5; + textStyleInner = textStyleInner.copyWith(color: NextColor.fgWhite40); + break; + case NextButtonStyle.critical: + backgroundColorInner = NextColor.bgCriticalDisabled; + textStyleInner = textStyleInner.copyWith(color: NextColor.fgWhite40); + case NextButtonStyle.outlined: + textStyleInner = textStyleInner.copyWith(color: NextColor.fgWhite40); + borderInner = Border.all(color: NextColor.borderDisabled); + } + } + + return NextButton._( + key: key, + text: text, + style: style, + size: size, + backgroundColor: backgroundColorInner, + textStyle: textStyleInner, + height: height ?? heightInner, + borderRadius: borderRadiusInner, + padding: paddingInner, + spacing: spacingInner, + border: borderInner, + onTap: onTap, + loading: loading, + disabled: disabled, + icon: icon, + width: width, + ); + } + + @override + Widget build(BuildContext context) { + final bool isInteractive = !loading && !disabled; + const duration = Duration(milliseconds: 160); + const curve = Curves.easeOut; + + return AnimatedContainer( + duration: duration, + curve: curve, + height: height, + width: width, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius, + border: border, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: isInteractive ? onTap : null, + borderRadius: borderRadius, + child: AnimatedPadding( + duration: duration, + curve: curve, + padding: EdgeInsets.symmetric(horizontal: padding, vertical: 0), + child: AnimatedDefaultTextStyle( + duration: duration, + curve: curve, + style: textStyle, + child: Row( + spacing: spacing, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: _getRow(), + ), + ), + ), + ), + ), + ); + } + + List _getRow() { + final List children = []; + + if (loading) { + children.add( + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(textStyle.color), + ), + ), + ); + return children; + } else if (icon != null) { + children.add(icon!); + } + + children.add(Flexible(child: Text(text, textAlign: TextAlign.center))); + + return children; + } +} diff --git a/client/lib/router/routes.dart b/client/lib/router/routes.dart index ca7ddcc4..3247dab2 100644 --- a/client/lib/router/routes.dart +++ b/client/lib/router/routes.dart @@ -13,6 +13,7 @@ import 'package:mobile/open/screens/instance/instance_screen.dart'; import 'package:mobile/open/screens/mfa/mfa_code_screen.dart'; import 'package:mobile/open/screens/process_qr_screen.dart'; import 'package:mobile/open/screens/scan_qr_screen.dart'; +import 'package:mobile/open/screens/testing/buttons_screen.dart'; import 'package:talker_flutter/talker_flutter.dart'; import '../logging.dart'; @@ -189,3 +190,15 @@ class BiometryFinishScreenRoute extends GoRouteData return BiometryFinishScreen(); } } + +@TypedGoRoute(path: "/testing/buttons") +@immutable +class ButtonsTestingScreenRoute extends GoRouteData + with $ButtonsTestingScreenRoute { + const ButtonsTestingScreenRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const ButtonsTestingScreen(); + } +} diff --git a/client/lib/router/routes.g.dart b/client/lib/router/routes.g.dart index 1b3fac66..d84d419d 100644 --- a/client/lib/router/routes.g.dart +++ b/client/lib/router/routes.g.dart @@ -21,6 +21,7 @@ List get $appRoutes => [ $biometrySetupScreenRoute, $biometrySetupFailedScreenRoute, $biometryFinishScreenRoute, + $buttonsTestingScreenRoute, ]; RouteBase get $processQrScreenRoute => GoRouteData.$route( @@ -430,3 +431,30 @@ mixin $BiometryFinishScreenRoute on GoRouteData { @override void replace(BuildContext context) => context.replace(location); } + +RouteBase get $buttonsTestingScreenRoute => GoRouteData.$route( + path: '/testing/buttons', + hasOverriddenOnExit: false, + factory: $ButtonsTestingScreenRoute._fromState, +); + +mixin $ButtonsTestingScreenRoute on GoRouteData { + static ButtonsTestingScreenRoute _fromState(GoRouterState state) => + const ButtonsTestingScreenRoute(); + + @override + String get location => GoRouteData.$location('/testing/buttons'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/client/lib/theme.dart b/client/lib/theme.dart index 73357da9..abedda03 100644 --- a/client/lib/theme.dart +++ b/client/lib/theme.dart @@ -3,6 +3,6 @@ import 'package:mobile/theme/color.dart'; final ThemeData defguardThemeData = ThemeData( useMaterial3: true, - colorScheme: ColorScheme.fromSeed(seedColor: Color(0x000c8ce0)), + colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0C8CE0)), scaffoldBackgroundColor: DgColor.navBg, ); diff --git a/client/lib/theme/next/color.dart b/client/lib/theme/next/color.dart new file mode 100644 index 00000000..50c23709 --- /dev/null +++ b/client/lib/theme/next/color.dart @@ -0,0 +1,176 @@ +// ignore_for_file: unused_field +import 'package:flutter/material.dart'; + +class _Primitive { + _Primitive._(); + + // White + static const Color white100 = Color(0xffffffff); + static const Color white90 = Color(0xe6ffffff); + static const Color white80 = Color(0xccffffff); + static const Color white70 = Color(0xb3ffffff); + static const Color white60 = Color(0x99ffffff); + static const Color white50 = Color(0x80ffffff); + static const Color white40 = Color(0x66ffffff); + static const Color white30 = Color(0x4dffffff); + static const Color white20 = Color(0x33ffffff); + static const Color white10 = Color(0x1affffff); + static const Color white5 = Color(0x0dffffff); + + // Dark Neutral + static const Color darkNeutral1400 = Color(0xff141517); + static const Color darkNeutral1300 = Color(0xff191a1c); + static const Color darkNeutral1200 = Color(0xff242629); + static const Color darkNeutral1100 = Color(0xff2e3136); + static const Color darkNeutral1000 = Color(0xff32363c); + static const Color darkNeutral900 = Color(0xff3d434b); + static const Color darkNeutral800 = Color(0xff4a5059); + static const Color darkNeutral700 = Color(0xff5e6672); + static const Color darkNeutral600 = Color(0xff7e8794); + static const Color darkNeutral500 = Color(0xff939ca9); + static const Color darkNeutral400 = Color(0xffa2acba); + static const Color darkNeutral300 = Color(0xffb8c0cd); + static const Color darkNeutral200 = Color(0xffdfe3e9); + static const Color darkNeutral100 = Color(0xfff0f2f5); + static const Color darkNeutral50 = Color(0xfff7f8fa); + + // Saturated Additional + static const Color saturatedError = Color(0xffcc3c3c); + static const Color saturatedWarning = Color(0xffff9500); + static const Color saturatedSuccess = Color(0xff74ffb8); + static const Color saturatedBlueNeutral = Color(0xff5073e1); + + // Saturated Dark Blue + static const Color saturatedDarkBlue100 = Color(0xff001989); + static const Color saturatedDarkBlue90 = Color(0xe6001989); + static const Color saturatedDarkBlue80 = Color(0xcc001989); + static const Color saturatedDarkBlue70 = Color(0xb3001989); + static const Color saturatedDarkBlue60 = Color(0x99001989); + static const Color saturatedDarkBlue50 = Color(0x80001989); + static const Color saturatedDarkBlue40 = Color(0x66001989); + static const Color saturatedDarkBlue30 = Color(0x4d001989); + static const Color saturatedDarkBlue20 = Color(0x33001989); + static const Color saturatedDarkBlue10 = Color(0x1a001989); + static const Color saturatedDarkBlue5 = Color(0x0d001989); + + // Saturated Green + static const Color saturatedGreen700 = Color(0xff024e17); + static const Color saturatedGreen600 = Color(0xff025a1b); + static const Color saturatedGreen500 = Color(0xff038026); + static const Color saturatedGreen400 = Color(0xff2e964b); + static const Color saturatedGreen300 = Color(0xff6db581); + static const Color saturatedGreen200 = Color(0xff98cba6); + static const Color saturatedGreen100 = Color(0xffe6f2e9); + static const Color saturatedGreen500Transparent = Color(0x14038026); + + // Saturated Orange + static const Color saturatedOrange700 = Color(0xff9c5b00); + static const Color saturatedOrange600 = Color(0xffb36800); + static const Color saturatedOrange500 = Color(0xffff9500); + static const Color saturatedOrange400 = Color(0xffffa72b); + static const Color saturatedOrange300 = Color(0xffffc26b); + static const Color saturatedOrange200 = Color(0xffffd496); + static const Color saturatedOrange100 = Color(0xfffff4e6); + static const Color saturatedOrange500Transparent = Color(0x14ff9500); + + // Saturated Violet + static const Color saturatedViolet700 = Color(0xff352452); + static const Color saturatedViolet600 = Color(0xff533780); + static const Color saturatedViolet500 = Color(0xff6637b2); + static const Color saturatedViolet400 = Color(0xff886aba); + static const Color saturatedViolet300 = Color(0xffac97cf); + static const Color saturatedViolet200 = Color(0xffc4b5dd); + static const Color saturatedViolet100 = Color(0xfff1edf7); + static const Color saturatedVioletTransparent = Color(0x146637b2); + + // Saturated Red + static const Color saturatedRed800 = Color(0xff23181a); + static const Color saturatedRed700 = Color(0xff7c2525); + static const Color saturatedRed600 = Color(0xff8f2a2a); + static const Color saturatedRed500 = Color(0xffcc3c3c); + static const Color saturatedRed400 = Color(0xffd55d5d); + static const Color saturatedRed300 = Color(0xffe18e8e); + static const Color saturatedRed200 = Color(0xffeaafaf); + static const Color saturatedRed100 = Color(0xfffaecec); + static const Color saturatedRed500Transparent = Color(0x33cc3c3c); + + // Saturated Blue + static const Color saturatedBlue800 = Color(0xff15181f); + static const Color saturatedBlue700 = Color(0xff263973); + static const Color saturatedBlue600 = Color(0xff334d9c); + static const Color saturatedBlue500 = Color(0xff3961db); + static const Color saturatedBlue400 = Color(0xff5777d9); + static const Color saturatedBlue300 = Color(0xff8ca1de); + static const Color saturatedBlue200 = Color(0xffb4c4f2); + static const Color saturatedBlue100 = Color(0xffedf1fc); + static const Color saturatedBlue50 = Color(0xfff9fafe); + static const Color saturatedBlue500Transparent = Color(0x14325cdb); +} + +class NextColor { + NextColor._(); + + // Background Colors - White + static const Color bgWhite100 = _Primitive.white100; + static const Color bgWhite90 = _Primitive.white90; + static const Color bgWhite80 = _Primitive.white80; + static const Color bgWhite70 = _Primitive.white70; + static const Color bgWhite60 = _Primitive.white60; + static const Color bgWhite50 = _Primitive.white50; + static const Color bgWhite40 = _Primitive.white40; + static const Color bgWhite30 = _Primitive.white30; + static const Color bgWhite20 = _Primitive.white20; + static const Color bgWhite10 = _Primitive.white10; + static const Color bgWhite5 = _Primitive.white5; + + // Foreground Colors - White + static const Color fgWhite100 = _Primitive.white100; + static const Color fgWhite90 = _Primitive.white90; + static const Color fgWhite80 = _Primitive.white80; + static const Color fgWhite70 = _Primitive.white70; + static const Color fgWhite60 = _Primitive.white60; + static const Color fgWhite50 = _Primitive.white50; + static const Color fgWhite40 = _Primitive.white40; + static const Color fgWhite30 = _Primitive.white30; + static const Color fgWhite20 = _Primitive.white20; + static const Color fgWhite10 = _Primitive.white10; + static const Color fgWhite5 = _Primitive.white5; + + // Background Colors - Semantic + static const Color bgCritical = _Primitive.saturatedRed500; + static const Color bgNeutral = _Primitive.saturatedBlueNeutral; + static const Color bgDarkBlue60 = _Primitive.saturatedDarkBlue60; + static const Color bgDarkBlue40 = _Primitive.saturatedDarkBlue40; + static const Color bgDarkBlue30 = _Primitive.saturatedDarkBlue30; + static const Color bgDarkBlue20 = _Primitive.saturatedDarkBlue20; + static const Color bgSuccess = _Primitive.saturatedSuccess; + static const Color bgWarning = _Primitive.saturatedOrange500; + static const Color bgCriticalFaded = _Primitive.saturatedRed400; + static const Color bgCriticalMuted = _Primitive.saturatedRed200; + static const Color bgCriticalDisabled = _Primitive.saturatedRed500Transparent; + + // Border Colors + static const Color borderBg = _Primitive.white100; + static const Color borderAction = _Primitive.white100; + static const Color borderActionDisabled = _Primitive.white20; + static const Color borderDefault = _Primitive.white40; + static const Color borderDisabled = _Primitive.white20; + static const Color borderEmphasis = _Primitive.white60; + static const Color borderMuted = _Primitive.white20; + static const Color borderFaded = _Primitive.white10; + static const Color borderCritical = _Primitive.saturatedRed200; + static const Color borderSuccess = _Primitive.saturatedSuccess; + static const Color borderWarning = _Primitive.saturatedOrange300; + + // Foreground Colors - Semantic + static const Color fgAction = _Primitive.saturatedBlue500; + static const Color fgAttention = _Primitive.saturatedOrange300; + static const Color fgCritical = _Primitive.saturatedRed200; + static const Color fgCriticalMuted = _Primitive.saturatedRed100; + static const Color fgBlack = _Primitive.darkNeutral1400; + static const Color fgFaded = _Primitive.darkNeutral900; + static const Color fgNeutral = _Primitive.darkNeutral800; + static const Color fgMuted = _Primitive.darkNeutral600; + static const Color fgDisabled = _Primitive.darkNeutral500; + static const Color fgSuccess = _Primitive.saturatedSuccess; +} diff --git a/client/lib/theme/next/spacing.dart b/client/lib/theme/next/spacing.dart new file mode 100644 index 00000000..c36e010e --- /dev/null +++ b/client/lib/theme/next/spacing.dart @@ -0,0 +1,17 @@ +class NextSpacing { + NextSpacing._(); + + static const double xs = 4; + static const double sm = 8; + static const double md = 12; + static const double lg = 16; + static const double xl = 20; + static const double xl2 = 24; + static const double xl3 = 32; + static const double xl4 = 40; + static const double xl5 = 48; + static const double xl6 = 64; + static const double xl7 = 80; + static const double xl8 = 96; + static const double xl9 = 120; +} diff --git a/client/lib/theme/next/text.dart b/client/lib/theme/next/text.dart new file mode 100644 index 00000000..347a95de --- /dev/null +++ b/client/lib/theme/next/text.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; + +class _FontFamily { + _FontFamily._(); + + static const geist = "Geist"; +} + +const String _defaultFontFamily = _FontFamily.geist; + +class NextText { + NextText._(); + + // Body - XXS + static const TextStyle bodyXxs600 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 11, + fontWeight: FontWeight.w600, + height: 14 / 11, + ); + static const TextStyle bodyXxs500 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 11, + fontWeight: FontWeight.w500, + height: 14 / 11, + ); + static const TextStyle bodyXxs400 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 11, + fontWeight: FontWeight.w400, + height: 14 / 11, + ); + + // Body - XS + static const TextStyle bodyXs600 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 12, + fontWeight: FontWeight.w600, + height: 16 / 12, + ); + static const TextStyle bodyXs500 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 16 / 12, + ); + static const TextStyle bodyXs400 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 12, + fontWeight: FontWeight.w400, + height: 16 / 12, + ); + + // Body - SM + static const TextStyle bodySm600 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w600, + height: 20 / 14, + ); + static const TextStyle bodySm500 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w500, + height: 20 / 14, + ); + static const TextStyle bodySm400 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 20 / 14, + ); + static const TextStyle bodySm300 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w300, + height: 20 / 14, + ); + + // Body - Primary + static const TextStyle bodyPrimary600 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 16, + fontWeight: FontWeight.w600, + height: 24 / 16, + ); + static const TextStyle bodyPrimary500 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 24 / 16, + ); + static const TextStyle bodyPrimary400 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 16, + fontWeight: FontWeight.w400, + height: 24 / 16, + ); + + // Titles + static const TextStyle h1 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 32, + fontWeight: FontWeight.w600, + height: 44 / 32, + ); + static const TextStyle h2 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 28, + fontWeight: FontWeight.w600, + height: 40 / 28, + ); + static const TextStyle h3 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 24, + fontWeight: FontWeight.w600, + height: 32 / 24, + ); + static const TextStyle h4 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 20, + fontWeight: FontWeight.w600, + height: 28 / 20, + ); + static const TextStyle h5 = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 18, + fontWeight: FontWeight.w600, + height: 28 / 18, + ); + + // Inputs + static const TextStyle inputTitle = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 16 / 12, + ); + static const TextStyle inputPrimary = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 20 / 14, + ); + static const TextStyle inputBig = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 16, + fontWeight: FontWeight.w400, + height: 20 / 16, + ); + static const TextStyle inputError = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 12, + fontWeight: FontWeight.w400, + height: 16 / 12, + ); + + // Menu + static const TextStyle menuTitle = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 12, + fontWeight: FontWeight.w600, + height: 2, + ); + static const TextStyle menuText = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 24 / 14, + ); + + // Buttons + static const TextStyle buttonLabelBig = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w600, + ); + static const TextStyle buttonLabelPrimary = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w600, + ); + static const TextStyle buttonLabelSecondary = TextStyle( + fontFamily: _defaultFontFamily, + fontSize: 14, + fontWeight: FontWeight.w500, + ); +} diff --git a/client/pubspec.lock b/client/pubspec.lock index 3d270feb..c0611ede 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: f8db46d2ea9ff6f3a37191a7fd5b7813da1253ace8c32c1b9eadace41d1188ea + sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc" url: "https://pub.dev" source: hosted - version: "7.2.1" + version: "7.0.0" app_links_linux: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: app_links_platform_interface - sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809" + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.2" app_links_web: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "79e05eaf15a48d7230b053a4363b8eaac0cc234bbd0134c3229455481f55cbc6" + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 url: "https://pub.dev" source: hosted - version: "4.1.5" + version: "4.1.2" build_runner: dependency: "direct dev" description: @@ -365,10 +365,10 @@ packages: dependency: transitive description: name: dbus - sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91" + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" url: "https://pub.dev" source: hosted - version: "0.7.13" + version: "0.7.14" device_info_plus: dependency: "direct main" description: @@ -554,10 +554,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff" + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -772,10 +772,10 @@ packages: dependency: transitive description: name: image - sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.9.1" + version: "4.8.0" intl: dependency: transitive description: @@ -876,10 +876,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: fdb936d59ab945c7af297defd67bd1ed87b11b6db1bc16d01e94677a8f1c38ec + sha256: b201c006fa769c23386f89aa6837ec0eb8179fcfb212eadcf87b422b3f9a6a78 url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.8" local_auth_darwin: dependency: transitive description: @@ -1284,10 +1284,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 url: "https://pub.dev" source: hosted - version: "2.4.27" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: @@ -1577,10 +1577,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" url: "https://pub.dev" source: hosted - version: "6.3.32" + version: "6.3.30" url_launcher_ios: dependency: transitive description: @@ -1760,10 +1760,10 @@ packages: dependency: transitive description: name: xml - sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.6.1" yaml: dependency: transitive description: @@ -1781,5 +1781,5 @@ packages: source: hosted version: "2.2.4" sdks: - dart: ">=3.12.0 <4.0.0" - flutter: ">=3.44.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/client/pubspec.yaml b/client/pubspec.yaml index d18ed859..4d063b68 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.7.0+1 +version: 2.1.0+1 environment: sdk: ^3.8.1 @@ -130,6 +130,19 @@ flutter: weight: 500 - asset: assets/fonts/poppins-600.ttf weight: 600 + - family: Geist + fonts: + - asset: assets/fonts/geist-300.ttf + weight: 300 + - asset: assets/fonts/geist-400.ttf + weight: 400 + - asset: assets/fonts/geist-500.ttf + weight: 500 + - asset: assets/fonts/geist-600.ttf + weight: 600 + - family: JetBrainsMono + fonts: + - asset: assets/fonts/jetbrains-regular.ttf # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg diff --git a/wireguard_plugin/pubspec.yaml b/wireguard_plugin/pubspec.yaml index 49e05722..0a06c71c 100644 --- a/wireguard_plugin/pubspec.yaml +++ b/wireguard_plugin/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.0.1 environment: sdk: ^3.8.1 - flutter: '>=3.3.0' + flutter: '>=3.4.0' dependencies: flutter: From 50ee2ae09f292fe7342d723709d658753ccbaad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 6 Aug 2026 16:26:33 +0200 Subject: [PATCH 45/65] bump gradle to 9.0.1 drop explicit kotlin package --- client/android/app/build.gradle.kts | 1 - client/android/gradle/wrapper/gradle-wrapper.properties | 2 +- client/android/settings.gradle.kts | 2 +- wireguard_plugin/android/build.gradle | 7 +++---- wireguard_plugin/example/android/app/build.gradle.kts | 1 - .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- wireguard_plugin/example/android/settings.gradle.kts | 2 +- 7 files changed, 7 insertions(+), 10 deletions(-) diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 760519ff..73364bba 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("com.android.application") - id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties index 53aec703..f98345f0 100644 --- a/client/android/gradle/wrapper/gradle-wrapper.properties +++ b/client/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-all.zip diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts index 0604b23a..e3f1cdcf 100644 --- a/client/android/settings.gradle.kts +++ b/client/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.11.0" apply false + id("com.android.application") version "9.0.1" apply false id("org.jetbrains.kotlin.android") version "2.2.0" apply false } diff --git a/wireguard_plugin/android/build.gradle b/wireguard_plugin/android/build.gradle index 90606ef2..8fb1c1a3 100644 --- a/wireguard_plugin/android/build.gradle +++ b/wireguard_plugin/android/build.gradle @@ -1,13 +1,13 @@ buildscript { - ext.kotlin_version = "2.1.0" + ext.kotlin_version = "2.2.0" repositories { google() mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.7.3") + classpath("com.android.tools.build:gradle:9.0.1") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version") } @@ -15,8 +15,7 @@ buildscript { plugins { id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.0' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0' } group = "net.defguard.wireguard_plugin" diff --git a/wireguard_plugin/example/android/app/build.gradle.kts b/wireguard_plugin/example/android/app/build.gradle.kts index d99c5573..7d778d1a 100644 --- a/wireguard_plugin/example/android/app/build.gradle.kts +++ b/wireguard_plugin/example/android/app/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("com.android.application") - id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } diff --git a/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties index ac3b4792..f98345f0 100644 --- a/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-all.zip diff --git a/wireguard_plugin/example/android/settings.gradle.kts b/wireguard_plugin/example/android/settings.gradle.kts index ab39a10a..a7a72566 100644 --- a/wireguard_plugin/example/android/settings.gradle.kts +++ b/wireguard_plugin/example/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "9.0.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } From b580766a3453601233d1eb07c599686e660b3614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Fri, 7 Aug 2026 10:31:17 +0200 Subject: [PATCH 46/65] migrate project to flutter 3.44 snapshot from template upgrade wireguard_plugin project fix serialization plugin declaration --- client/android/app/build.gradle.kts | 15 +- client/android/gradle.properties | 6 +- .../gradle/gradle-daemon-jvm.properties | 12 + client/android/settings.gradle.kts | 2 +- .../open/screens/testing/buttons_screen.dart | 2 +- client/pubspec.lock | 2 +- client/pubspec.yaml | 2 +- wireguard_plugin/android/build.gradle | 75 -- wireguard_plugin/android/build.gradle.kts | 80 ++ wireguard_plugin/android/settings.gradle | 1 - wireguard_plugin/android/settings.gradle.kts | 1 + .../wireguard_plugin/WireguardPlugin.kt | 13 +- wireguard_plugin/example/.gitignore | 49 -- wireguard_plugin/example/README.md | 16 - .../example/analysis_options.yaml | 28 - wireguard_plugin/example/android/.gitignore | 14 - .../example/android/app/build.gradle.kts | 43 - .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 45 - .../wireguard_plugin_example/MainActivity.kt | 5 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - .../example/android/build.gradle.kts | 21 - .../example/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 5 - .../example/android/settings.gradle.kts | 25 - .../plugin_integration_test.dart | 13 - wireguard_plugin/example/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 26 - .../example/ios/Flutter/Debug.xcconfig | 2 - .../example/ios/Flutter/Release.xcconfig | 2 - wireguard_plugin/example/ios/Podfile | 43 - wireguard_plugin/example/ios/Podfile.lock | 29 - .../ios/Runner.xcodeproj/project.pbxproj | 731 ---------------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 101 --- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../example/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 --- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 - .../ios/Runner/Base.lproj/Main.storyboard | 26 - .../example/ios/Runner/Info.plist | 49 -- .../ios/Runner/Runner-Bridging-Header.h | 1 - .../example/ios/RunnerTests/RunnerTests.swift | 27 - wireguard_plugin/example/lib/main.dart | 57 -- wireguard_plugin/example/macos/.gitignore | 7 - .../macos/Flutter/Flutter-Debug.xcconfig | 2 - .../macos/Flutter/Flutter-Release.xcconfig | 2 - .../Flutter/GeneratedPluginRegistrant.swift | 12 - wireguard_plugin/example/macos/Podfile | 42 - wireguard_plugin/example/macos/Podfile.lock | 23 - .../macos/Runner.xcodeproj/project.pbxproj | 801 ------------------ .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 99 --- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../example/macos/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 68 -- .../AppIcon.appiconset/app_icon_1024.png | Bin 102994 -> 0 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 5680 -> 0 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 520 -> 0 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 14142 -> 0 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1066 -> 0 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 36406 -> 0 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2218 -> 0 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 -------- .../macos/Runner/Configs/AppInfo.xcconfig | 14 - .../macos/Runner/Configs/Debug.xcconfig | 2 - .../macos/Runner/Configs/Release.xcconfig | 2 - .../macos/Runner/Configs/Warnings.xcconfig | 13 - .../macos/Runner/DebugProfile.entitlements | 12 - .../example/macos/Runner/Info.plist | 32 - .../macos/Runner/MainFlutterWindow.swift | 15 - .../example/macos/Runner/Release.entitlements | 8 - .../macos/RunnerTests/RunnerTests.swift | 28 - wireguard_plugin/example/pubspec.lock | 315 ------- wireguard_plugin/example/pubspec.yaml | 86 -- .../example/test/widget_test.dart | 27 - wireguard_plugin/pubspec.yaml | 4 +- 111 files changed, 121 insertions(+), 3804 deletions(-) create mode 100644 client/android/gradle/gradle-daemon-jvm.properties delete mode 100644 wireguard_plugin/android/build.gradle create mode 100644 wireguard_plugin/android/build.gradle.kts delete mode 100644 wireguard_plugin/android/settings.gradle create mode 100644 wireguard_plugin/android/settings.gradle.kts delete mode 100644 wireguard_plugin/example/.gitignore delete mode 100644 wireguard_plugin/example/README.md delete mode 100644 wireguard_plugin/example/analysis_options.yaml delete mode 100644 wireguard_plugin/example/android/.gitignore delete mode 100644 wireguard_plugin/example/android/app/build.gradle.kts delete mode 100644 wireguard_plugin/example/android/app/src/debug/AndroidManifest.xml delete mode 100644 wireguard_plugin/example/android/app/src/main/AndroidManifest.xml delete mode 100644 wireguard_plugin/example/android/app/src/main/kotlin/net/defguard/wireguard_plugin_example/MainActivity.kt delete mode 100644 wireguard_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 wireguard_plugin/example/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 wireguard_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 wireguard_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 wireguard_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 wireguard_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 wireguard_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 wireguard_plugin/example/android/app/src/main/res/values-night/styles.xml delete mode 100644 wireguard_plugin/example/android/app/src/main/res/values/styles.xml delete mode 100644 wireguard_plugin/example/android/app/src/profile/AndroidManifest.xml delete mode 100644 wireguard_plugin/example/android/build.gradle.kts delete mode 100644 wireguard_plugin/example/android/gradle.properties delete mode 100644 wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 wireguard_plugin/example/android/settings.gradle.kts delete mode 100644 wireguard_plugin/example/integration_test/plugin_integration_test.dart delete mode 100644 wireguard_plugin/example/ios/.gitignore delete mode 100644 wireguard_plugin/example/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 wireguard_plugin/example/ios/Flutter/Debug.xcconfig delete mode 100644 wireguard_plugin/example/ios/Flutter/Release.xcconfig delete mode 100644 wireguard_plugin/example/ios/Podfile delete mode 100644 wireguard_plugin/example/ios/Podfile.lock delete mode 100644 wireguard_plugin/example/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 wireguard_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 wireguard_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 wireguard_plugin/example/ios/Runner/AppDelegate.swift delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 wireguard_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 wireguard_plugin/example/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 wireguard_plugin/example/ios/Runner/Info.plist delete mode 100644 wireguard_plugin/example/ios/Runner/Runner-Bridging-Header.h delete mode 100644 wireguard_plugin/example/ios/RunnerTests/RunnerTests.swift delete mode 100644 wireguard_plugin/example/lib/main.dart delete mode 100644 wireguard_plugin/example/macos/.gitignore delete mode 100644 wireguard_plugin/example/macos/Flutter/Flutter-Debug.xcconfig delete mode 100644 wireguard_plugin/example/macos/Flutter/Flutter-Release.xcconfig delete mode 100644 wireguard_plugin/example/macos/Flutter/GeneratedPluginRegistrant.swift delete mode 100644 wireguard_plugin/example/macos/Podfile delete mode 100644 wireguard_plugin/example/macos/Podfile.lock delete mode 100644 wireguard_plugin/example/macos/Runner.xcodeproj/project.pbxproj delete mode 100644 wireguard_plugin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 wireguard_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 wireguard_plugin/example/macos/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 wireguard_plugin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 wireguard_plugin/example/macos/Runner/AppDelegate.swift delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png delete mode 100644 wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png delete mode 100644 wireguard_plugin/example/macos/Runner/Base.lproj/MainMenu.xib delete mode 100644 wireguard_plugin/example/macos/Runner/Configs/AppInfo.xcconfig delete mode 100644 wireguard_plugin/example/macos/Runner/Configs/Debug.xcconfig delete mode 100644 wireguard_plugin/example/macos/Runner/Configs/Release.xcconfig delete mode 100644 wireguard_plugin/example/macos/Runner/Configs/Warnings.xcconfig delete mode 100644 wireguard_plugin/example/macos/Runner/DebugProfile.entitlements delete mode 100644 wireguard_plugin/example/macos/Runner/Info.plist delete mode 100644 wireguard_plugin/example/macos/Runner/MainFlutterWindow.swift delete mode 100644 wireguard_plugin/example/macos/Runner/Release.entitlements delete mode 100644 wireguard_plugin/example/macos/RunnerTests/RunnerTests.swift delete mode 100644 wireguard_plugin/example/pubspec.lock delete mode 100644 wireguard_plugin/example/pubspec.yaml delete mode 100644 wireguard_plugin/example/test/widget_test.dart diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 73364bba..1a74208c 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { android { namespace = "net.defguard.mobile" - compileSdk = 36 + compileSdk = 37 ndkVersion = flutter.ndkVersion compileOptions { @@ -15,14 +15,10 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - defaultConfig { applicationId = "net.defguard.mobile" minSdk = 31 - targetSdk = 36 + targetSdk = 37 versionCode = flutter.versionCode versionName = flutter.versionName } @@ -38,6 +34,13 @@ android { } } + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") implementation("com.google.android.gms:play-services-cronet:18.1.1") diff --git a/client/android/gradle.properties b/client/android/gradle.properties index 24863d21..123ea484 100644 --- a/client/android/gradle.properties +++ b/client/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +android.newDsl=false +android.builtInKotlin=false + + diff --git a/client/android/gradle/gradle-daemon-jvm.properties b/client/android/gradle/gradle-daemon-jvm.properties new file mode 100644 index 00000000..fa4ed510 --- /dev/null +++ b/client/android/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cf726b4a1c84b50457225f9bba6d7650/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/fa1e318c287360478e3c83a9a3ef1007/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cf726b4a1c84b50457225f9bba6d7650/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/fa1e318c287360478e3c83a9a3ef1007/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c2dd35c9d0aaf0ba6ad0791320f99dfc/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/e5810bd7fd1f8a586644409d395a7e55/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cf726b4a1c84b50457225f9bba6d7650/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/fa1e318c287360478e3c83a9a3ef1007/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/7b3c4877c0749019e6805bb61e421497/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d76df094a9cbbabd3b08251f9e61444a/redirect +toolchainVersion=25 diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts index e3f1cdcf..bf7df2ad 100644 --- a/client/android/settings.gradle.kts +++ b/client/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "9.0.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false } include(":app") diff --git a/client/lib/open/screens/testing/buttons_screen.dart b/client/lib/open/screens/testing/buttons_screen.dart index 25cbd896..07364605 100644 --- a/client/lib/open/screens/testing/buttons_screen.dart +++ b/client/lib/open/screens/testing/buttons_screen.dart @@ -71,7 +71,7 @@ class ButtonsTestingScreen extends StatelessWidget { child: Text( 'Size: ${size.name}', style: TextStyle( - color: Colors.white.withOpacity(0.7), + color: Colors.white.withValues(alpha: 0.7), fontSize: 14, ), ), diff --git a/client/pubspec.lock b/client/pubspec.lock index c0611ede..867abae3 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1781,5 +1781,5 @@ packages: source: hosted version: "2.2.4" sdks: - dart: ">=3.10.3 <4.0.0" + dart: ">=3.12.2 <4.0.0" flutter: ">=3.38.4" diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 4d063b68..70769e21 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 2.1.0+1 environment: - sdk: ^3.8.1 + sdk: ^3.12.2 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/wireguard_plugin/android/build.gradle b/wireguard_plugin/android/build.gradle deleted file mode 100644 index 8fb1c1a3..00000000 --- a/wireguard_plugin/android/build.gradle +++ /dev/null @@ -1,75 +0,0 @@ - -buildscript { - ext.kotlin_version = "2.2.0" - repositories { - google() - mavenCentral() - } - - dependencies { - classpath("com.android.tools.build:gradle:9.0.1") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") - classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version") - } -} - -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0' -} - -group = "net.defguard.wireguard_plugin" -version = "1.0-SNAPSHOT" - -allprojects { - repositories { - google() - mavenCentral() - } -} - - -android { - namespace = "net.defguard.wireguard_plugin" - - compileSdk = 36 - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11 - } - - sourceSets { - main.java.srcDirs += "src/main/kotlin" - test.java.srcDirs += "src/test/kotlin" - } - - defaultConfig { - minSdk = 31 - } - - dependencies { - testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.mockito:mockito-core:5.0.0") - } - - testOptions { - unitTests.all { - useJUnitPlatform() - - testLogging { - events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} - showStandardStreams = true - } - } - } -} -dependencies { - compileOnly files('../../lib/tunnel.aar') - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0' -} diff --git a/wireguard_plugin/android/build.gradle.kts b/wireguard_plugin/android/build.gradle.kts new file mode 100644 index 00000000..6313f077 --- /dev/null +++ b/wireguard_plugin/android/build.gradle.kts @@ -0,0 +1,80 @@ +group = "net.defguard.wireguard_plugin" +version = "1.0-SNAPSHOT" + +buildscript { + val kotlinVersion = "2.3.20" + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:9.0.1") + classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.plugin.serialization") version "2.3.20" +} + +android { + namespace = "net.defguard.wireguard_plugin" + + compileSdk = 36 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + sourceSets { + getByName("main") { + java.srcDirs("src/main/kotlin") + } + getByName("test") { + java.srcDirs("src/test/kotlin") + } + } + + defaultConfig { + minSdk = 31 + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + all { + it.useJUnitPlatform() + + it.outputs.upToDateWhen { false } + + it.testLogging { + events("passed", "skipped", "failed", "standardOut", "standardError") + showStandardStreams = true + } + } + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +dependencies { + compileOnly(files("../../lib/tunnel.aar")) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.mockito:mockito-core:5.0.0") +} diff --git a/wireguard_plugin/android/settings.gradle b/wireguard_plugin/android/settings.gradle deleted file mode 100644 index b0e015eb..00000000 --- a/wireguard_plugin/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'wireguard_plugin' diff --git a/wireguard_plugin/android/settings.gradle.kts b/wireguard_plugin/android/settings.gradle.kts new file mode 100644 index 00000000..41c24d93 --- /dev/null +++ b/wireguard_plugin/android/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "wireguard_plugin" diff --git a/wireguard_plugin/android/src/main/kotlin/net/defguard/wireguard_plugin/WireguardPlugin.kt b/wireguard_plugin/android/src/main/kotlin/net/defguard/wireguard_plugin/WireguardPlugin.kt index 8d6dfb6c..3add22b4 100644 --- a/wireguard_plugin/android/src/main/kotlin/net/defguard/wireguard_plugin/WireguardPlugin.kt +++ b/wireguard_plugin/android/src/main/kotlin/net/defguard/wireguard_plugin/WireguardPlugin.kt @@ -1,5 +1,6 @@ package net.defguard.wireguard_plugin +import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent @@ -30,6 +31,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy import java.util.Timer import java.util.TimerTask +import kotlin.time.Duration.Companion.milliseconds @OptIn(ExperimentalSerializationApi::class) val json = Json { @@ -57,6 +59,7 @@ class WireguardPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, @JvmStatic private var activeTunnelData: ActiveTunnelData? = null @JvmStatic + @SuppressLint("StaticFieldLeak") private var backend: GoBackend? = null @JvmStatic private var futureBackend: CompletableDeferred? = null @@ -106,7 +109,7 @@ class WireguardPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Globals.pendingRecoveryEvent?.let { tunnelData -> Log.i(LOG_TAG, "Sending pending recovery event for active tunnel") scope.launch(Dispatchers.Main) { - delay(50) // Small delay to ensure event sink is ready + delay(50.milliseconds) // Small delay to ensure event sink is ready emitEvent(WireguardPluginEvent.TUNNEL_UP, json.encodeToString(tunnelData)) Globals.pendingRecoveryEvent = null // Clear after sending } @@ -207,7 +210,7 @@ class WireguardPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, private fun createBackend(): GoBackend { if (Globals.backend == null) { - Globals.backend = GoBackend(context); + Globals.backend = GoBackend(context.applicationContext); } return Globals.backend as GoBackend; } @@ -350,7 +353,7 @@ class WireguardPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, // Start periodic health checks Globals.healthCheckTimer = Timer("HealthCheckTimer", true) - Globals.healthCheckTimer?.scheduleAtFixedRate(object : TimerTask() { + Globals.healthCheckTimer?.schedule(object : TimerTask() { override fun run() { performHealthCheck() } @@ -375,9 +378,9 @@ class WireguardPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, // Try to get tunnel stats from the backend scope.launch(Dispatchers.IO) { val stats = futureBackend.await().getStatistics(tunnel) - stats?.let { tunnelStats -> + stats.let { tunnelStats -> val currentDownloadBytes = tunnelStats.totalRx() - + // Check if there's been any data transfer since last check if (currentDownloadBytes > Globals.lastDownloadBytes) { Globals.lastDownloadBytes = currentDownloadBytes diff --git a/wireguard_plugin/example/.gitignore b/wireguard_plugin/example/.gitignore deleted file mode 100644 index 53022561..00000000 --- a/wireguard_plugin/example/.gitignore +++ /dev/null @@ -1,49 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ -.swiftpm/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release - -# direnv stuff -.direnv/ -.envrc diff --git a/wireguard_plugin/example/README.md b/wireguard_plugin/example/README.md deleted file mode 100644 index e6f4843e..00000000 --- a/wireguard_plugin/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# wireguard_plugin_example - -Demonstrates how to use the wireguard_plugin plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/wireguard_plugin/example/analysis_options.yaml b/wireguard_plugin/example/analysis_options.yaml deleted file mode 100644 index 0d290213..00000000 --- a/wireguard_plugin/example/analysis_options.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/wireguard_plugin/example/android/.gitignore b/wireguard_plugin/example/android/.gitignore deleted file mode 100644 index be3943c9..00000000 --- a/wireguard_plugin/example/android/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks diff --git a/wireguard_plugin/example/android/app/build.gradle.kts b/wireguard_plugin/example/android/app/build.gradle.kts deleted file mode 100644 index 7d778d1a..00000000 --- a/wireguard_plugin/example/android/app/build.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -plugins { - id("com.android.application") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "net.defguard.wireguard_plugin_example" - compileSdk = 36 - ndkVersion = flutter.ndkVersion - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "net.defguard.wireguard_plugin_example" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 31 - targetSdk = 36 - versionCode = flutter.versionCode - versionName = flutter.versionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - } - } -} - -flutter { - source = "../.." -} diff --git a/wireguard_plugin/example/android/app/src/debug/AndroidManifest.xml b/wireguard_plugin/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f6981..00000000 --- a/wireguard_plugin/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/wireguard_plugin/example/android/app/src/main/AndroidManifest.xml b/wireguard_plugin/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 4232bba7..00000000 --- a/wireguard_plugin/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/wireguard_plugin/example/android/app/src/main/kotlin/net/defguard/wireguard_plugin_example/MainActivity.kt b/wireguard_plugin/example/android/app/src/main/kotlin/net/defguard/wireguard_plugin_example/MainActivity.kt deleted file mode 100644 index 3e83d6d3..00000000 --- a/wireguard_plugin/example/android/app/src/main/kotlin/net/defguard/wireguard_plugin_example/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.defguard.wireguard_plugin_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/wireguard_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/wireguard_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f3..00000000 --- a/wireguard_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/wireguard_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/wireguard_plugin/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f8..00000000 --- a/wireguard_plugin/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/wireguard_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/wireguard_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/wireguard_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/wireguard_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/wireguard_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wireguard_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/wireguard_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/wireguard_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/wireguard_plugin/example/android/app/src/main/res/values-night/styles.xml b/wireguard_plugin/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be7..00000000 --- a/wireguard_plugin/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/wireguard_plugin/example/android/app/src/main/res/values/styles.xml b/wireguard_plugin/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef880..00000000 --- a/wireguard_plugin/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/wireguard_plugin/example/android/app/src/profile/AndroidManifest.xml b/wireguard_plugin/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981..00000000 --- a/wireguard_plugin/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/wireguard_plugin/example/android/build.gradle.kts b/wireguard_plugin/example/android/build.gradle.kts deleted file mode 100644 index 89176ef4..00000000 --- a/wireguard_plugin/example/android/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/wireguard_plugin/example/android/gradle.properties b/wireguard_plugin/example/android/gradle.properties deleted file mode 100644 index f018a618..00000000 --- a/wireguard_plugin/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true diff --git a/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index f98345f0..00000000 --- a/wireguard_plugin/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-all.zip diff --git a/wireguard_plugin/example/android/settings.gradle.kts b/wireguard_plugin/example/android/settings.gradle.kts deleted file mode 100644 index a7a72566..00000000 --- a/wireguard_plugin/example/android/settings.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - val flutterSdkPath = run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false -} - -include(":app") diff --git a/wireguard_plugin/example/integration_test/plugin_integration_test.dart b/wireguard_plugin/example/integration_test/plugin_integration_test.dart deleted file mode 100644 index 627b6561..00000000 --- a/wireguard_plugin/example/integration_test/plugin_integration_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -// This is a basic Flutter integration test. -// -// Since integration tests run in a full Flutter application, they can interact -// with the host side of a plugin implementation, unlike Dart unit tests. -// -// For more information about Flutter integration tests, please see -// https://flutter.dev/to/integration-testing - -import 'package:integration_test/integration_test.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); -} diff --git a/wireguard_plugin/example/ios/.gitignore b/wireguard_plugin/example/ios/.gitignore deleted file mode 100644 index 7a7f9873..00000000 --- a/wireguard_plugin/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/wireguard_plugin/example/ios/Flutter/AppFrameworkInfo.plist b/wireguard_plugin/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 7c569640..00000000 --- a/wireguard_plugin/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 12.0 - - diff --git a/wireguard_plugin/example/ios/Flutter/Debug.xcconfig b/wireguard_plugin/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6f..00000000 --- a/wireguard_plugin/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/wireguard_plugin/example/ios/Flutter/Release.xcconfig b/wireguard_plugin/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bfe..00000000 --- a/wireguard_plugin/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/wireguard_plugin/example/ios/Podfile b/wireguard_plugin/example/ios/Podfile deleted file mode 100644 index e549ee22..00000000 --- a/wireguard_plugin/example/ios/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '12.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/wireguard_plugin/example/ios/Podfile.lock b/wireguard_plugin/example/ios/Podfile.lock deleted file mode 100644 index 9680dc44..00000000 --- a/wireguard_plugin/example/ios/Podfile.lock +++ /dev/null @@ -1,29 +0,0 @@ -PODS: - - Flutter (1.0.0) - - integration_test (0.0.1): - - Flutter - - wireguard_plugin (0.0.1): - - Flutter - - FlutterMacOS - -DEPENDENCIES: - - Flutter (from `Flutter`) - - integration_test (from `.symlinks/plugins/integration_test/ios`) - - wireguard_plugin (from `.symlinks/plugins/wireguard_plugin/darwin`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - integration_test: - :path: ".symlinks/plugins/integration_test/ios" - wireguard_plugin: - :path: ".symlinks/plugins/wireguard_plugin/darwin" - -SPEC CHECKSUMS: - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e - wireguard_plugin: 4230683e11339baa26b5daa8c124ec0aa870dc49 - -PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5 - -COCOAPODS: 1.16.2 diff --git a/wireguard_plugin/example/ios/Runner.xcodeproj/project.pbxproj b/wireguard_plugin/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index a0631bd5..00000000 --- a/wireguard_plugin/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,731 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3D8E9C9216F1FDB4D35248AA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD59271CDC5BF97DCD8013FC /* Pods_Runner.framework */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 7D5AAB6B2A44A6AE6FE50A69 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F1C884E7ACC47450D4FE567 /* Pods_RunnerTests.framework */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1B687074BA24E11B74D644DB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 37C2E61F214189F3F02D5CD4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3F1C884E7ACC47450D4FE567 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 66EA817E8F722868452F3BD0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AE99D4F8C975F14B25E7B060 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - C0833ECA85B82717A748AD30 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - F8B2164ED7355A9E2B890C06 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - FD59271CDC5BF97DCD8013FC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3D8E9C9216F1FDB4D35248AA /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B00B6469A3D8ABEF228A527C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D5AAB6B2A44A6AE6FE50A69 /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 15727F557C6BEFA9715FBAD9 /* Pods */ = { - isa = PBXGroup; - children = ( - 1B687074BA24E11B74D644DB /* Pods-Runner.debug.xcconfig */, - C0833ECA85B82717A748AD30 /* Pods-Runner.release.xcconfig */, - 37C2E61F214189F3F02D5CD4 /* Pods-Runner.profile.xcconfig */, - F8B2164ED7355A9E2B890C06 /* Pods-RunnerTests.debug.xcconfig */, - AE99D4F8C975F14B25E7B060 /* Pods-RunnerTests.release.xcconfig */, - 66EA817E8F722868452F3BD0 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 529B2AD920F173B0AE5D2543 /* Frameworks */ = { - isa = PBXGroup; - children = ( - FD59271CDC5BF97DCD8013FC /* Pods_Runner.framework */, - 3F1C884E7ACC47450D4FE567 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - 15727F557C6BEFA9715FBAD9 /* Pods */, - 529B2AD920F173B0AE5D2543 /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - F3B8768FB7B62ED69F4BB2FC /* [CP] Check Pods Manifest.lock */, - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - B00B6469A3D8ABEF228A527C /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - E6493940CC56303CAD8406D7 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 7B52FA3F345E3A1E97676D2B /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 7B52FA3F345E3A1E97676D2B /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - E6493940CC56303CAD8406D7 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - F3B8768FB7B62ED69F4BB2FC /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 82GZ7KN29J; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F8B2164ED7355A9E2B890C06 /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AE99D4F8C975F14B25E7B060 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 66EA817E8F722868452F3BD0 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 82GZ7KN29J; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 82GZ7KN29J; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a6..00000000 --- a/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5..00000000 --- a/wireguard_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/wireguard_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/wireguard_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index e3773d42..00000000 --- a/wireguard_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wireguard_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/wireguard_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14..00000000 --- a/wireguard_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5..00000000 --- a/wireguard_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/wireguard_plugin/example/ios/Runner/AppDelegate.swift b/wireguard_plugin/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 62666446..00000000 --- a/wireguard_plugin/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab..00000000 --- a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2f..00000000 --- a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b..00000000 --- a/wireguard_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/wireguard_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/wireguard_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7..00000000 --- a/wireguard_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wireguard_plugin/example/ios/Runner/Base.lproj/Main.storyboard b/wireguard_plugin/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516..00000000 --- a/wireguard_plugin/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wireguard_plugin/example/ios/Runner/Info.plist b/wireguard_plugin/example/ios/Runner/Info.plist deleted file mode 100644 index fc309d06..00000000 --- a/wireguard_plugin/example/ios/Runner/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Wireguard Plugin - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - wireguard_plugin_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - diff --git a/wireguard_plugin/example/ios/Runner/Runner-Bridging-Header.h b/wireguard_plugin/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a56..00000000 --- a/wireguard_plugin/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/wireguard_plugin/example/ios/RunnerTests/RunnerTests.swift b/wireguard_plugin/example/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 89c25086..00000000 --- a/wireguard_plugin/example/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Flutter -import UIKit -import XCTest - - -@testable import wireguard_plugin - -// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. -// -// See https://developer.apple.com/documentation/xctest for more information about using XCTest. - -class RunnerTests: XCTestCase { - - func testGetPlatformVersion() { - let plugin = WireguardPlugin() - - let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) - - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) - resultExpectation.fulfill() - } - waitForExpectations(timeout: 1) - } - -} diff --git a/wireguard_plugin/example/lib/main.dart b/wireguard_plugin/example/lib/main.dart deleted file mode 100644 index f1212b2e..00000000 --- a/wireguard_plugin/example/lib/main.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:talker/talker.dart'; -import 'dart:async'; - -import 'package:wireguard_plugin/wireguard_plugin.dart'; - -final talker = Talker(); - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - final _wireguardPlugin = WireguardPlugin(talker: talker); - bool permissionsGranted = false; - - @override - void initState() { - super.initState(); - } - - Future requestPermissions() async { - final result = await _wireguardPlugin.requestPermissions(); - setState(() { - permissionsGranted = result; - }); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: const Text('Plugin example app')), - body: Column( - children: [ - Center(child: Text("Permissions state: $permissionsGranted")), - Center( - child: ElevatedButton( - onPressed: () { - requestPermissions(); - }, - child: Text("Request permissions"), - ), - ), - ], - ), - ), - ); - } -} diff --git a/wireguard_plugin/example/macos/.gitignore b/wireguard_plugin/example/macos/.gitignore deleted file mode 100644 index 746adbb6..00000000 --- a/wireguard_plugin/example/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/wireguard_plugin/example/macos/Flutter/Flutter-Debug.xcconfig b/wireguard_plugin/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index 4b81f9b2..00000000 --- a/wireguard_plugin/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/wireguard_plugin/example/macos/Flutter/Flutter-Release.xcconfig b/wireguard_plugin/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index 5caa9d15..00000000 --- a/wireguard_plugin/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/wireguard_plugin/example/macos/Flutter/GeneratedPluginRegistrant.swift b/wireguard_plugin/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 77afec7e..00000000 --- a/wireguard_plugin/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import wireguard_plugin - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - WireguardPlugin.register(with: registry.registrar(forPlugin: "WireguardPlugin")) -} diff --git a/wireguard_plugin/example/macos/Podfile b/wireguard_plugin/example/macos/Podfile deleted file mode 100644 index a46f7f23..00000000 --- a/wireguard_plugin/example/macos/Podfile +++ /dev/null @@ -1,42 +0,0 @@ -platform :osx, '11.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/wireguard_plugin/example/macos/Podfile.lock b/wireguard_plugin/example/macos/Podfile.lock deleted file mode 100644 index 421c00a8..00000000 --- a/wireguard_plugin/example/macos/Podfile.lock +++ /dev/null @@ -1,23 +0,0 @@ -PODS: - - FlutterMacOS (1.0.0) - - wireguard_plugin (0.0.1): - - Flutter - - FlutterMacOS - -DEPENDENCIES: - - FlutterMacOS (from `Flutter/ephemeral`) - - wireguard_plugin (from `Flutter/ephemeral/.symlinks/plugins/wireguard_plugin/darwin`) - -EXTERNAL SOURCES: - FlutterMacOS: - :path: Flutter/ephemeral - wireguard_plugin: - :path: Flutter/ephemeral/.symlinks/plugins/wireguard_plugin/darwin - -SPEC CHECKSUMS: - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - wireguard_plugin: 4230683e11339baa26b5daa8c124ec0aa870dc49 - -PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82 - -COCOAPODS: 1.16.2 diff --git a/wireguard_plugin/example/macos/Runner.xcodeproj/project.pbxproj b/wireguard_plugin/example/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 1c394c93..00000000 --- a/wireguard_plugin/example/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,801 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 18ECED03CF1D4FECC394612E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5E112EFC2CB72096B7C232C /* Pods_Runner.framework */; }; - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - F032566C065A5419C08A854B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE2D730955F67C90B80E634B /* Pods_RunnerTests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC10EC2044A3C60003C045; - remoteInfo = Runner; - }; - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 31F9DCB37E739A2747C2190C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* wireguard_plugin_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wireguard_plugin_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 6C2D2E573417BE9B545C1593 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 75BBACEF06536BF07261595B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - BE2D730955F67C90B80E634B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D3ED89F1C12F2F1F3602AAC6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - DBBF056B6ECE4A59E0F51F19 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - DF6FABE2711A1E36271C3952 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - F5E112EFC2CB72096B7C232C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 331C80D2294CF70F00263BE5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F032566C065A5419C08A854B /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 18ECED03CF1D4FECC394612E /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C80D6294CF71000263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C80D7294CF71000263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 331C80D6294CF71000263BE5 /* RunnerTests */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - 58BB87F40D9BEAE5EAC070B9 /* Pods */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* wireguard_plugin_example.app */, - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - 58BB87F40D9BEAE5EAC070B9 /* Pods */ = { - isa = PBXGroup; - children = ( - DBBF056B6ECE4A59E0F51F19 /* Pods-Runner.debug.xcconfig */, - DF6FABE2711A1E36271C3952 /* Pods-Runner.release.xcconfig */, - 6C2D2E573417BE9B545C1593 /* Pods-Runner.profile.xcconfig */, - D3ED89F1C12F2F1F3602AAC6 /* Pods-RunnerTests.debug.xcconfig */, - 31F9DCB37E739A2747C2190C /* Pods-RunnerTests.release.xcconfig */, - 75BBACEF06536BF07261595B /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - F5E112EFC2CB72096B7C232C /* Pods_Runner.framework */, - BE2D730955F67C90B80E634B /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C80D4294CF70F00263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - A69387B52D69468446224A03 /* [CP] Check Pods Manifest.lock */, - 331C80D1294CF70F00263BE5 /* Sources */, - 331C80D2294CF70F00263BE5 /* Frameworks */, - 331C80D3294CF70F00263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C80DA294CF71000263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 2FB67017CF6222309419A344 /* [CP] Check Pods Manifest.lock */, - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - DEF63B93A44D196F1B4127BA /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* wireguard_plugin_example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C80D4294CF70F00263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 33CC10EC2044A3C60003C045; - }; - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 331C80D4294CF70F00263BE5 /* RunnerTests */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C80D3294CF70F00263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 2FB67017CF6222309419A344 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; - A69387B52D69468446224A03 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - DEF63B93A44D196F1B4127BA /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C80D1294CF70F00263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC10EC2044A3C60003C045 /* Runner */; - targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; - }; - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 331C80DB294CF71000263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D3ED89F1C12F2F1F3602AAC6 /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wireguard_plugin_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wireguard_plugin_example"; - }; - name = Debug; - }; - 331C80DC294CF71000263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 31F9DCB37E739A2747C2190C /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wireguard_plugin_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wireguard_plugin_example"; - }; - name = Release; - }; - 331C80DD294CF71000263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 75BBACEF06536BF07261595B /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wireguard_plugin_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wireguard_plugin_example"; - }; - name = Profile; - }; - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C80DB294CF71000263BE5 /* Debug */, - 331C80DC294CF71000263BE5 /* Release */, - 331C80DD294CF71000263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/wireguard_plugin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/wireguard_plugin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/wireguard_plugin/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/wireguard_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/wireguard_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index d49b629b..00000000 --- a/wireguard_plugin/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wireguard_plugin/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/wireguard_plugin/example/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14..00000000 --- a/wireguard_plugin/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/wireguard_plugin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/wireguard_plugin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/wireguard_plugin/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/wireguard_plugin/example/macos/Runner/AppDelegate.swift b/wireguard_plugin/example/macos/Runner/AppDelegate.swift deleted file mode 100644 index b3c17614..00000000 --- a/wireguard_plugin/example/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Cocoa -import FlutterMacOS - -@main -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } - - override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { - return true - } -} diff --git a/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a2ec33f1..00000000 --- a/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 82b6f9d9a33e198f5747104729e1fcef999772a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102994 zcmeEugo5nb1G~3xi~y`}h6XHx5j$(L*3|5S2UfkG$|UCNI>}4f?MfqZ+HW-sRW5RKHEm z^unW*Xx{AH_X3Xdvb%C(Bh6POqg==@d9j=5*}oEny_IS;M3==J`P0R!eD6s~N<36C z*%-OGYqd0AdWClO!Z!}Y1@@RkfeiQ$Ib_ z&fk%T;K9h`{`cX3Hu#?({4WgtmkR!u3ICS~|NqH^fdNz>51-9)OF{|bRLy*RBv#&1 z3Oi_gk=Y5;>`KbHf~w!`u}!&O%ou*Jzf|Sf?J&*f*K8cftMOKswn6|nb1*|!;qSrlw= zr-@X;zGRKs&T$y8ENnFU@_Z~puu(4~Ir)>rbYp{zxcF*!EPS6{(&J}qYpWeqrPWW< zfaApz%<-=KqxrqLLFeV3w0-a0rEaz9&vv^0ZfU%gt9xJ8?=byvNSb%3hF^X_n7`(fMA;C&~( zM$cQvQ|g9X)1AqFvbp^B{JEX$o;4iPi?+v(!wYrN{L}l%e#5y{j+1NMiT-8=2VrCP zmFX9=IZyAYA5c2!QO96Ea-6;v6*$#ZKM-`%JCJtrA3d~6h{u+5oaTaGE)q2b+HvdZ zvHlY&9H&QJ5|uG@wDt1h99>DdHy5hsx)bN`&G@BpxAHh$17yWDyw_jQhhjSqZ=e_k z_|r3=_|`q~uA47y;hv=6-o6z~)gO}ZM9AqDJsR$KCHKH;QIULT)(d;oKTSPDJ}Jx~G#w-(^r<{GcBC*~4bNjfwHBumoPbU}M)O za6Hc2ik)2w37Yyg!YiMq<>Aov?F2l}wTe+>h^YXcK=aesey^i)QC_p~S zp%-lS5%)I29WfywP(r4@UZ@XmTkqo51zV$|U|~Lcap##PBJ}w2b4*kt7x6`agP34^ z5fzu_8rrH+)2u*CPcr6I`gL^cI`R2WUkLDE5*PX)eJU@H3HL$~o_y8oMRoQ0WF9w| z6^HZDKKRDG2g;r8Z4bn+iJNFV(CG;K-j2>aj229gl_C6n12Jh$$h!}KVhn>*f>KcH z;^8s3t(ccVZ5<{>ZJK@Z`hn_jL{bP8Yn(XkwfRm?GlEHy=T($8Z1Mq**IM`zxN9>-yXTjfB18m_$E^JEaYn>pj`V?n#Xu;Z}#$- zw0Vw;T*&9TK$tKI7nBk9NkHzL++dZ^;<|F6KBYh2+XP-b;u`Wy{~79b%IBZa3h*3^ zF&BKfQ@Ej{7ku_#W#mNJEYYp=)bRMUXhLy2+SPMfGn;oBsiG_6KNL8{p1DjuB$UZB zA)a~BkL)7?LJXlCc}bB~j9>4s7tlnRHC5|wnycQPF_jLl!Avs2C3^lWOlHH&v`nGd zf&U!fn!JcZWha`Pl-B3XEe;(ks^`=Z5R zWyQR0u|do2`K3ec=YmWGt5Bwbu|uBW;6D8}J3{Uep7_>L6b4%(d=V4m#(I=gkn4HT zYni3cnn>@F@Wr<hFAY3Y~dW+3bte;70;G?kTn4Aw5nZ^s5|47 z4$rCHCW%9qa4)4vE%^QPMGf!ET!^LutY$G zqdT(ub5T5b+wi+OrV}z3msoy<4)`IPdHsHJggmog0K*pFYMhH!oZcgc5a)WmL?;TPSrerTVPp<#s+imF3v#!FuBNNa`#6 z!GdTCF|IIpz#(eV^mrYKThA4Bnv&vQet@%v9kuRu3EHx1-2-it@E`%9#u`)HRN#M? z7aJ{wzKczn#w^`OZ>Jb898^Xxq)0zd{3Tu7+{-sge-rQ z&0PME&wIo6W&@F|%Z8@@N3)@a_ntJ#+g{pUP7i?~3FirqU`rdf8joMG^ld?(9b7Iv z>TJgBg#)(FcW)h!_if#cWBh}f+V08GKyg|$P#KTS&%=!+0a%}O${0$i)kn9@G!}En zv)_>s?glPiLbbx)xk(lD-QbY(OP3;MSXM5E*P&_`Zks2@46n|-h$Y2L7B)iH{GAAq19h5-y0q>d^oy^y+soJu9lXxAe%jcm?=pDLFEG2kla40e!5a}mpe zdL=WlZ=@U6{>g%5a+y-lx)01V-x;wh%F{=qy#XFEAqcd+m}_!lQ)-9iiOL%&G??t| z?&NSdaLqdPdbQs%y0?uIIHY7rw1EDxtQ=DU!i{)Dkn~c$LG5{rAUYM1j5*G@oVn9~ zizz{XH(nbw%f|wI=4rw^6mNIahQpB)OQy10^}ACdLPFc2@ldVi|v@1nWLND?)53O5|fg`RZW&XpF&s3@c-R?aad!$WoH6u0B|}zt)L($E^@U- zO#^fxu9}Zw7Xl~nG1FVM6DZSR0*t!4IyUeTrnp@?)Z)*!fhd3)&s(O+3D^#m#bAem zpf#*aiG_0S^ofpm@9O7j`VfLU0+{$x!u^}3!zp=XST0N@DZTp!7LEVJgqB1g{psNr za0uVmh3_9qah14@M_pi~vAZ#jc*&aSm$hCNDsuQ-zPe&*Ii#2=2gP+DP4=DY z_Y0lUsyE6yaV9)K)!oI6+*4|spx2at*30CAx~6-5kfJzQ`fN8$!lz%hz^J6GY?mVH zbYR^JZ(Pmj6@vy-&!`$5soyy-NqB^8cCT40&R@|6s@m+ZxPs=Bu77-+Os7+bsz4nA3DrJ8#{f98ZMaj-+BD;M+Jk?pgFcZIb}m9N z{ct9T)Kye&2>l^39O4Q2@b%sY?u#&O9PO4@t0c$NUXG}(DZJ<;_oe2~e==3Z1+`Zo zFrS3ns-c}ZognVBHbg#e+1JhC(Yq7==rSJQ8J~}%94(O#_-zJKwnBXihl#hUd9B_>+T& z7eHHPRC?5ONaUiCF7w|{J`bCWS7Q&xw-Sa={j-f)n5+I=9s;E#fBQB$`DDh<^mGiF zu-m_k+)dkBvBO(VMe2O4r^sf3;sk9K!xgXJU>|t9Vm8Ty;fl5pZzw z9j|}ZD}6}t;20^qrS?YVPuPRS<39d^y0#O1o_1P{tN0?OX!lc-ICcHI@2#$cY}_CY zev|xdFcRTQ_H)1fJ7S0*SpPs8e{d+9lR~IZ^~dKx!oxz?=Dp!fD`H=LH{EeC8C&z-zK$e=!5z8NL=4zx2{hl<5z*hEmO=b-7(k5H`bA~5gT30Sjy`@-_C zKM}^so9Ti1B;DovHByJkTK87cfbF16sk-G>`Q4-txyMkyQS$d}??|Aytz^;0GxvOs zPgH>h>K+`!HABVT{sYgzy3CF5ftv6hI-NRfgu613d|d1cg^jh+SK7WHWaDX~hlIJ3 z>%WxKT0|Db1N-a4r1oPKtF--^YbP=8Nw5CNt_ZnR{N(PXI>Cm$eqi@_IRmJ9#)~ZHK_UQ8mi}w^`+4$OihUGVz!kW^qxnCFo)-RIDbA&k-Y=+*xYv5y4^VQ9S)4W5Pe?_RjAX6lS6Nz#!Hry=+PKx2|o_H_3M`}Dq{Bl_PbP(qel~P@=m}VGW*pK96 zI@fVag{DZHi}>3}<(Hv<7cVfWiaVLWr@WWxk5}GDEbB<+Aj;(c>;p1qmyAIj+R!`@#jf$ zy4`q23L-72Zs4j?W+9lQD;CYIULt%;O3jPWg2a%Zs!5OW>5h1y{Qof!p&QxNt5=T( zd5fy&7=hyq;J8%86YBOdc$BbIFxJx>dUyTh`L z-oKa=OhRK9UPVRWS`o2x53bAv+py)o)kNL6 z9W1Dlk-g6Ht@-Z^#6%`9S9`909^EMj?9R^4IxssCY-hYzei^TLq7Cj>z$AJyaU5=z zl!xiWvz0U8kY$etrcp8mL;sYqGZD!Hs-U2N{A|^oEKA482v1T%cs%G@X9M?%lX)p$ zZoC7iYTPe8yxY0Jne|s)fCRe1mU=Vb1J_&WcIyP|x4$;VSVNC`M+e#oOA`#h>pyU6 z?7FeVpk`Hsu`~T3i<_4<5fu?RkhM;@LjKo6nX>pa%8dSdgPO9~Jze;5r>Tb1Xqh5q z&SEdTXevV@PT~!O6z|oypTk7Qq+BNF5IQ(8s18c=^0@sc8Gi|3e>VKCsaZ?6=rrck zl@oF5Bd0zH?@15PxSJIRroK4Wa?1o;An;p0#%ZJ^tI=(>AJ2OY0GP$E_3(+Zz4$AQ zW)QWl<4toIJ5TeF&gNXs>_rl}glkeG#GYbHHOv-G!%dJNoIKxn)FK$5&2Zv*AFic! z@2?sY&I*PSfZ8bU#c9fdIJQa_cQijnj39-+hS@+~e*5W3bj%A}%p9N@>*tCGOk+cF zlcSzI6j%Q|2e>QG3A<86w?cx6sBtLNWF6_YR?~C)IC6_10SNoZUHrCpp6f^*+*b8` zlx4ToZZuI0XW1W)24)92S)y0QZa);^NRTX6@gh8@P?^=#2dV9s4)Q@K+gnc{6|C}& zDLHr7nDOLrsH)L@Zy{C_2UrYdZ4V{|{c8&dRG;wY`u>w%$*p>PO_}3`Y21pk?8Wtq zGwIXTulf7AO2FkPyyh2TZXM1DJv>hI`}x`OzQI*MBc#=}jaua&czSkI2!s^rOci|V zFkp*Vbiz5vWa9HPFXMi=BV&n3?1?%8#1jq?p^3wAL`jgcF)7F4l<(H^!i=l-(OTDE zxf2p71^WRIExLf?ig0FRO$h~aA23s#L zuZPLkm>mDwBeIu*C7@n@_$oSDmdWY7*wI%aL73t~`Yu7YwE-hxAATmOi0dmB9|D5a zLsR7OQcA0`vN9m0L|5?qZ|jU+cx3_-K2!K$zDbJ$UinQy<9nd5ImWW5n^&=Gg>Gsh zY0u?m1e^c~Ug39M{{5q2L~ROq#c{eG8Oy#5h_q=#AJj2Yops|1C^nv0D1=fBOdfAG z%>=vl*+_w`&M7{qE#$xJJp_t>bSh7Mpc(RAvli9kk3{KgG5K@a-Ue{IbU{`umXrR3ra5Y7xiX42+Q%N&-0#`ae_ z#$Y6Wa++OPEDw@96Zz##PFo9sADepQe|hUy!Zzc2C(L`k9&=a8XFr+!hIS>D2{pdGP1SzwyaGLiH3j--P>U#TWw90t8{8Bt%m7Upspl#=*hS zhy|(XL6HOqBW}Og^tLX7 z+`b^L{O&oqjwbxDDTg2B;Yh2(fW>%S5Pg8^u1p*EFb z`(fbUM0`afawYt%VBfD&b3MNJ39~Ldc@SAuzsMiN%E}5{uUUBc7hc1IUE~t-Y9h@e7PC|sv$xGx=hZiMXNJxz5V(np%6u{n24iWX#!8t#>Ob$in<>dw96H)oGdTHnU zSM+BPss*5)Wz@+FkooMxxXZP1{2Nz7a6BB~-A_(c&OiM)UUNoa@J8FGxtr$)`9;|O z(Q?lq1Q+!E`}d?KemgC!{nB1JJ!B>6J@XGQp9NeQvtbM2n7F%v|IS=XWPVZY(>oq$ zf=}8O_x`KOxZoGnp=y24x}k6?gl_0dTF!M!T`={`Ii{GnT1jrG9gPh)R=RZG8lIR| z{ZJ6`x8n|y+lZuy${fuEDTAf`OP!tGySLXD}ATJO5UoZv|Xo3%7O~L63+kw}v)Ci=&tWx3bQJfL@5O18CbPlkR^IcKA zy1=^Vl-K-QBP?9^R`@;czcUw;Enbbyk@vJQB>BZ4?;DM%BUf^eZE+sOy>a){qCY6Y znYy;KGpch-zf=5|p#SoAV+ie8M5(Xg-{FoLx-wZC9IutT!(9rJ8}=!$!h%!J+vE2e z(sURwqCC35v?1>C1L)swfA^sr16{yj7-zbT6Rf26-JoEt%U?+|rQ zeBuGohE?@*!zR9)1P|3>KmJSgK*fOt>N>j}LJB`>o(G#Dduvx7@DY7};W7K;Yj|8O zGF<+gTuoIKe7Rf+LQG3-V1L^|E;F*}bQ-{kuHq}| ze_NwA7~US19sAZ)@a`g*zkl*ykv2v3tPrb4Og2#?k6Lc7@1I~+ew48N&03hW^1Cx+ zfk5Lr4-n=#HYg<7ka5i>2A@ZeJ60gl)IDX!!p zzfXZQ?GrT>JEKl7$SH!otzK6=0dIlqN)c23YLB&Krf9v-{@V8p+-e2`ujFR!^M%*; ze_7(Jh$QgoqwB!HbX=S+^wqO15O_TQ0-qX8f-|&SOuo3ZE{{9Jw5{}>MhY}|GBhO& zv48s_B=9aYQfa;d>~1Z$y^oUUaDer>7ve5+Gf?rIG4GZ!hRKERlRNgg_C{W_!3tsI2TWbX8f~MY)1Q`6Wj&JJ~*;ay_0@e zzx+mE-pu8{cEcVfBqsnm=jFU?H}xj@%CAx#NO>3 z_re3Rq%d1Y7VkKy{=S73&p;4^Praw6Y59VCP6M?!Kt7{v#DG#tz?E)`K95gH_mEvb z%$<~_mQ$ad?~&T=O0i0?`YSp?E3Dj?V>n+uTRHAXn`l!pH9Mr}^D1d@mkf+;(tV45 zH_yfs^kOGLXlN*0GU;O&{=awxd?&`{JPRr$z<1HcAO2K`K}92$wC}ky&>;L?#!(`w z68avZGvb728!vgw>;8Z8I@mLtI`?^u6R>sK4E7%=y)jpmE$fH!Dj*~(dy~-2A5Cm{ zl{1AZw`jaDmfvaB?jvKwz!GC}@-Dz|bFm1OaPw(ia#?>vF7Y5oh{NVbyD~cHB1KFn z9C@f~X*Wk3>sQH9#D~rLPslAd26@AzMh=_NkH_yTNXx6-AdbAb z{Ul89YPHslD?xAGzOlQ*aMYUl6#efCT~WI zOvyiewT=~l1W(_2cEd(8rDywOwjM-7P9!8GCL-1<9KXXO=6%!9=W++*l1L~gRSxLVd8K=A7&t52ql=J&BMQu{fa6y zXO_e>d?4X)xp2V8e3xIQGbq@+vo#&n>-_WreTTW0Yr?|YRPP43cDYACMQ(3t6(?_k zfgDOAU^-pew_f5U#WxRXB30wcfDS3;k~t@b@w^GG&<5n$Ku?tT(%bQH(@UHQGN)N|nfC~7?(etU`}XB)$>KY;s=bYGY#kD%i9fz= z2nN9l?UPMKYwn9bX*^xX8Y@%LNPFU>s#Ea1DaP%bSioqRWi9JS28suTdJycYQ+tW7 zrQ@@=13`HS*dVKaVgcem-45+buD{B;mUbY$YYULhxK)T{S?EB<8^YTP$}DA{(&)@S zS#<8S96y9K2!lG^VW-+CkfXJIH;Vo6wh)N}!08bM$I7KEW{F6tqEQ?H@(U zAqfi%KCe}2NUXALo;UN&k$rU0BLNC$24T_mcNY(a@lxR`kqNQ0z%8m>`&1ro40HX} z{{3YQ;2F9JnVTvDY<4)x+88i@MtXE6TBd7POk&QfKU-F&*C`isS(T_Q@}K)=zW#K@ zbXpcAkTT-T5k}Wj$dMZl7=GvlcCMt}U`#Oon1QdPq%>9J$rKTY8#OmlnNWBYwafhx zqFnym@okL#Xw>4SeRFejBnZzY$jbO)e^&&sHBgMP%Ygfi!9_3hp17=AwLBNFTimf0 zw6BHNXw19Jg_Ud6`5n#gMpqe%9!QB^_7wAYv8nrW94A{*t8XZu0UT&`ZHfkd(F{Px zD&NbRJP#RX<=+sEeGs2`9_*J2OlECpR;4uJie-d__m*(aaGE}HIo+3P{my@;a~9Y$ zHBXVJ83#&@o6{M+pE9^lI<4meLLFN_3rwgR4IRyp)~OF0n+#ORrcJ2_On9-78bWbG zuCO0esc*n1X3@p1?lN{qWS?l7J$^jbpeel{w~51*0CM+q9@9X=>%MF(ce~om(}?td zjkUmdUR@LOn-~6LX#=@a%rvj&>DFEoQscOvvC@&ZB5jVZ-;XzAshwx$;Qf@U41W=q zOSSjQGQV8Qi3*4DngNMIM&Cxm7z*-K`~Bl(TcEUxjQ1c=?)?wF8W1g;bAR%sM#LK( z_Op?=P%)Z+J!>vpN`By0$?B~Out%P}kCriDq@}In&fa_ZyKV+nLM0E?hfxuu%ciUz z>yAk}OydbWNl7{)#112j&qmw;*Uj&B;>|;Qwfc?5wIYIHH}s6Mve@5c5r+y)jK9i( z_}@uC(98g)==AGkVN?4>o@w=7x9qhW^ zB(b5%%4cHSV?3M?k&^py)j*LK16T^Ef4tb05-h-tyrjt$5!oo4spEfXFK7r_Gfv7#x$bsR7T zs;dqxzUg9v&GjsQGKTP*=B(;)be2aN+6>IUz+Hhw-n>^|`^xu*xvjGPaDoFh2W4-n z@Wji{5Y$m>@Vt7TE_QVQN4*vcfWv5VY-dT0SV=l=8LAEq1go*f zkjukaDV=3kMAX6GAf0QOQHwP^{Z^=#Lc)sh`QB)Ftl&31jABvq?8!3bt7#8vxB z53M{4{GR4Hl~;W3r}PgXSNOt477cO62Yj(HcK&30zsmWpvAplCtpp&mC{`2Ue*Bwu zF&UX1;w%`Bs1u%RtGPFl=&sHu@Q1nT`z={;5^c^^S~^?2-?<|F9RT*KQmfgF!7=wD@hytxbD;=9L6PZrK*1<4HMObNWehA62DtTy)q5H|57 z9dePuC!1;0MMRRl!S@VJ8qG=v^~aEU+}2Qx``h1LII!y{crP2ky*R;Cb;g|r<#ryo zju#s4dE?5CTIZKc*O4^3qWflsQ(voX>(*_JP7>Q&$%zCAIBTtKC^JUi@&l6u&t0hXMXjz_y!;r@?k|OU9aD%938^TZ>V? zqJmom_6dz4DBb4Cgs_Ef@}F%+cRCR%UMa9pi<-KHN;t#O@cA%(LO1Rb=h?5jiTs93 zPLR78p+3t>z4|j=<>2i4b`ketv}9Ax#B0)hn7@bFl;rDfP8p7u9XcEb!5*PLKB(s7wQC2kzI^@ae)|DhNDmSy1bOLid%iIap@24A(q2XI!z_hkl-$1T10 z+KKugG4-}@u8(P^S3PW4x>an;XWEF-R^gB{`t8EiP{ZtAzoZ!JRuMRS__-Gg#Qa3{<;l__CgsF+nfmFNi}p z>rV!Y6B@cC>1up)KvaEQiAvQF!D>GCb+WZsGHjDeWFz?WVAHP65aIA8u6j6H35XNYlyy8>;cWe3ekr};b;$9)0G`zsc9LNsQ&D?hvuHRpBxH)r-1t9|Stc*u<}Ol&2N+wPMom}d15_TA=Aprp zjN-X3*Af$7cDWMWp##kOH|t;c2Pa9Ml4-)o~+7P;&q8teF-l}(Jt zTGKOQqJTeT!L4d}Qw~O0aanA$Vn9Rocp-MO4l*HK)t%hcp@3k0%&_*wwpKD6ThM)R z8k}&7?)YS1ZYKMiy?mn>VXiuzX7$Ixf7EW8+C4K^)m&eLYl%#T=MC;YPvD&w#$MMf zQ=>`@rh&&r!@X&v%ZlLF42L_c=5dSU^uymKVB>5O?AouR3vGv@ei%Z|GX5v1GK2R* zi!!}?+-8>J$JH^fPu@)E6(}9$d&9-j51T^n-e0Ze%Q^)lxuex$IL^XJ&K2oi`wG}QVGk2a7vC4X?+o^z zsCK*7`EUfSuQA*K@Plsi;)2GrayQOG9OYF82Hc@6aNN5ulqs1Of-(iZQdBI^U5of^ zZg2g=Xtad7$hfYu6l~KDQ}EU;oIj(3nO#u9PDz=eO3(iax7OCmgT2p_7&^3q zg7aQ;Vpng*)kb6=sd5?%j5Dm|HczSChMo8HHq_L8R;BR5<~DVyU$8*Tk5}g0eW5x7 z%d)JFZ{(Y<#OTKLBA1fwLM*fH7Q~7Sc2Ne;mVWqt-*o<;| z^1@vo_KTYaMnO$7fbLL+qh#R$9bvnpJ$RAqG+z8h|} z3F5iwG*(sCn9Qbyg@t0&G}3fE0jGq3J!JmG2K&$urx^$z95) z7h?;4vE4W=v)uZ*Eg3M^6f~|0&T)2D;f+L_?M*21-I1pnK(pT$5l#QNlT`SidYw~o z{`)G)Asv#cue)Ax1RNWiRUQ(tQ(bzd-f2U4xlJK+)ZWBxdq#fp=A>+Qc%-tl(c)`t z$e2Ng;Rjvnbu7((;v4LF9Y1?0el9hi!g>G{^37{ z`^s-03Z5jlnD%#Mix19zkU_OS|86^_x4<0(*YbPN}mi-$L?Z4K(M|2&VV*n*ZYN_UqI?eKZi3!b)i z%n3dzUPMc-dc|q}TzvPy!VqsEWCZL(-eURDRG4+;Eu!LugSSI4Fq$Ji$Dp08`pfP_C5Yx~`YKcywlMG;$F z)R5!kVml_Wv6MSpeXjG#g?kJ0t_MEgbXlUN3k|JJ%N>|2xn8yN>>4qxh!?dGI}s|Y zDTKd^JCrRSN+%w%D_uf=Tj6wIV$c*g8D96jb^Kc#>5Fe-XxKC@!pIJw0^zu;`_yeb zhUEm-G*C=F+jW%cP(**b61fTmPn2WllBr4SWNdKe*P8VabZsh0-R|?DO=0x`4_QY) zR7sthW^*BofW7{Sak&S1JdiG?e=SfL24Y#w_)xrBVhGB-13q$>mFU|wd9Xqe-o3{6 zSn@@1@&^)M$rxb>UmFuC+pkio#T;mSnroMVZJ%nZ!uImi?%KsIX#@JU2VY(`kGb1A z7+1MEG)wd@)m^R|a2rXeviv$!emwcY(O|M*xV!9%tBzarBOG<4%gI9SW;Um_gth4=gznYzOFd)y8e+3APCkL)i-OI`;@7-mCJgE`js(M} z;~ZcW{{FMVVO)W>VZ}ILouF#lWGb%Couu}TI4kubUUclW@jEn6B_^v!Ym*(T*4HF9 zWhNKi8%sS~viSdBtnrq!-Dc5(G^XmR>DFx8jhWvR%*8!m*b*R8e1+`7{%FACAK`7 zzdy8TmBh?FVZ0vtw6npnWwM~XjF2fNvV#ZlGG z?FxHkXHN>JqrBYoPo$)zNC7|XrQfcqmEXWud~{j?La6@kbHG@W{xsa~l1=%eLly8B z4gCIH05&Y;6O2uFSopNqP|<$ml$N40^ikxw0`o<~ywS1(qKqQN!@?Ykl|bE4M?P+e zo$^Vs_+x)iuw?^>>`$&lOQOUkZ5>+OLnRA)FqgpDjW&q*WAe(_mAT6IKS9;iZBl8M z<@=Y%zcQUaSBdrs27bVK`c$)h6A1GYPS$y(FLRD5Yl8E3j0KyH08#8qLrsc_qlws; znMV%Zq8k+&T2kf%6ZO^2=AE9>?a587g%-={X}IS~P*I(NeCF9_9&`)|ok0iiIun zo+^odT0&Z4k;rn7I1v87=z!zKU(%gfB$(1mrRYeO$sbqM22Kq68z9wgdg8HBxp>_< zn9o%`f?sVO=IN#5jSX&CGODWlZfQ9A)njK2O{JutYwRZ?n0G_p&*uwpE`Md$iQxrd zoQfF^b8Ou)+3BO_3_K5y*~?<(BF@1l+@?Z6;^;U>qlB)cdro;rxOS1M{Az$s^9o5sXDCg8yD<=(pKI*0e zLk>@lo#&s0)^*Q+G)g}C0IErqfa9VbL*Qe=OT@&+N8m|GJF7jd83vY#SsuEv2s{Q> z>IpoubNs>D_5?|kXGAPgF@mb_9<%hjU;S0C8idI)a=F#lPLuQJ^7OnjJlH_Sks9JD zMl1td%YsWq3YWhc;E$H1<0P$YbSTqs`JKY%(}svsifz|h8BHguL82dBl+z0^YvWk8 zGy;7Z0v5_FJ2A$P0wIr)lD?cPR%cz>kde!=W%Ta^ih+Dh4UKdf7ip?rBz@%y2&>`6 zM#q{JXvW9ZlaSk1oD!n}kSmcDa2v6T^Y-dy+#fW^y>eS8_%<7tWXUp8U@s$^{JFfKMjDAvR z$YmVB;n3ofl!ro9RNT!TpQpcycXCR}$9k5>IPWDXEenQ58os?_weccrT+Bh5sLoiH zZ_7~%t(vT)ZTEO= zb0}@KaD{&IyK_sd8b$`Qz3%UA`nSo zn``!BdCeN!#^G;lK@G2ron*0jQhbdw)%m$2;}le@z~PSLnU-z@tL)^(p%P>OO^*Ff zNRR9oQ`W+x^+EU+3BpluwK77|B3=8QyT|$V;02bn_LF&3LhLA<#}{{)jE)}CiW%VEU~9)SW+=F%7U-iYlQ&q!#N zwI2{(h|Pi&<8_fqvT*}FLN^0CxN}#|3I9G_xmVg$gbn2ZdhbmGk7Q5Q2Tm*ox8NMo zv`iaZW|ZEOMyQga5fts?&T-eCCC9pS0mj7v0SDkD=*^MxurP@89v&Z#3q{FM!a_nr zb?KzMv`BBFOew>4!ft@A&(v-kWXny-j#egKef|#!+3>26Qq0 zv!~8ev4G`7Qk>V1TaMT-&ziqoY3IJp8_S*%^1j73D|=9&;tDZH^!LYFMmME4*Wj(S zRt~Q{aLb_O;wi4u&=}OYuj}Lw*j$@z*3>4&W{)O-oi@9NqdoU!=U%d|se&h?^$Ip# z)BY+(1+cwJz!yy4%l(aLC;T!~Ci>yAtXJb~b*yr&v7f{YCU8P|N1v~H`xmGsG)g)y z4%mv=cPd`s7a*#OR7f0lpD$ueP>w8qXj0J&*7xX+U!uat5QNk>zwU$0acn5p=$88L=jn_QCSYkTV;1~(yUem#0gB`FeqY98sf=>^@ z_MCdvylv~WL%y_%y_FE1)j;{Szj1+K7Lr_y=V+U zk6Tr;>XEqlEom~QGL!a+wOf(@ZWoxE<$^qHYl*H1a~kk^BLPn785%nQb$o;Cuz0h& za9LMx^bKEbPS%e8NM33Jr|1T|ELC(iE!FUci38xW_Y7kdHid#2ie+XZhP;2!Z;ZAM zB_cXKm)VrPK!SK|PY00Phwrpd+x0_Aa;}cDQvWKrwnQrqz##_gvHX2ja?#_{f#;bz`i>C^^ zTLDy;6@HZ~XQi7rph!mz9k!m;KchA)uMd`RK4WLK7)5Rl48m#l>b(#`WPsl<0j z-sFkSF6>Nk|LKnHtZ`W_NnxZP62&w)S(aBmmjMDKzF%G;3Y?FUbo?>b5;0j8Lhtc4 zr*8d5Y9>g@FFZaViw7c16VsHcy0u7M%6>cG1=s=Dtx?xMJSKIu9b6GU8$uSzf43Y3 zYq|U+IWfH;SM~*N1v`KJo!|yfLxTFS?oHsr3qvzeVndVV^%BWmW6re_S!2;g<|Oao z+N`m#*i!)R%i1~NO-xo{qpwL0ZrL7hli;S z3L0lQ_z}z`fdK39Mg~Zd*%mBdD;&5EXa~@H(!###L`ycr7gW`f)KRuqyHL3|uyy3h zSS^td#E&Knc$?dXs*{EnPYOp^-vjAc-h4z#XkbG&REC7;0>z^^Z}i8MxGKerEY z>l?(wReOlXEsNE5!DO&ZWyxY)gG#FSZs%fXuzA~XIAPVp-%yb2XLSV{1nH6{)5opg z(dZKckn}Q4Li-e=eUDs1Psg~5zdn1>ql(*(nn6)iD*OcVkwmKL(A{fix(JhcVB&}V zVt*Xb!{gzvV}dc446>(D=SzfCu7KB`oMjv6kPzSv&B>>HLSJP|wN`H;>oRw*tl#N) z*zZ-xwM7D*AIsBfgqOjY1Mp9aq$kRa^dZU_xw~KxP;|q(m+@e+YSn~`wEJzM|Ippb zzb@%;hB7iH4op9SqmX?j!KP2chsb79(mFossBO-Zj8~L}9L%R%Bw<`^X>hjkCY5SG z7lY!8I2mB#z)1o;*3U$G)3o0A&{0}#B;(zPd2`OF`Gt~8;0Re8nIseU z_yzlf$l+*-wT~_-cYk$^wTJ@~7i@u(CZs9FVkJCru<*yK8&>g+t*!JqCN6RH%8S-P zxH8+Cy#W?!;r?cLMC(^BtAt#xPNnwboI*xWw#T|IW^@3|q&QYY6Ehxoh@^URylR|T zne-Y6ugE^7p5bkRDWIh)?JH5V^ub82l-LuVjDr7UT^g`q4dB&mBFRWGL_C?hoeL(% zo}ocH5t7|1Mda}T!^{Qt9vmA2ep4)dQSZO>?Eq8}qRp&ZJ?-`Tnw+MG(eDswP(L*X3ahC2Ad0_wD^ff9hfzb%Jd`IXx5 zae@NMzBXJDwJS?7_%!TB^E$N8pvhOHDK$7YiOelTY`6KX8hK6YyT$tk*adwN>s^Kp zwM3wGVPhwKU*Yq-*BCs}l`l#Tej(NQ>jg*S0TN%D+GcF<14Ms6J`*yMY;W<-mMN&-K>((+P}+t+#0KPGrzjP zJ~)=Bcz%-K!L5ozIWqO(LM)l_9lVOc4*S65&DKM#TqsiWNG{(EZQw!bc>qLW`=>p-gVJ;T~aN2D_- z{>SZC=_F+%hNmH6ub%Ykih0&YWB!%sd%W5 zHC2%QMP~xJgt4>%bU>%6&uaDtSD?;Usm}ari0^fcMhi_)JZgb1g5j zFl4`FQ*%ROfYI}e7RIq^&^a>jZF23{WB`T>+VIxj%~A-|m=J7Va9FxXV^%UwccSZd zuWINc-g|d6G5;95*%{e;9S(=%yngpfy+7ao|M7S|Jb0-4+^_q-uIqVS&ufU880UDH*>(c)#lt2j zzvIEN>>$Y(PeALC-D?5JfH_j+O-KWGR)TKunsRYKLgk7eu4C{iF^hqSz-bx5^{z0h ze2+u>Iq0J4?)jIo)}V!!m)%)B;a;UfoJ>VRQ*22+ncpe9f4L``?v9PH&;5j{WF?S_C>Lq>nkChZB zjF8(*v0c(lU^ZI-)_uGZnnVRosrO4`YinzI-RSS-YwjYh3M`ch#(QMNw*)~Et7Qpy z{d<3$4FUAKILq9cCZpjvKG#yD%-juhMj>7xIO&;c>_7qJ%Ae8Z^m)g!taK#YOW3B0 zKKSMOd?~G4h}lrZbtPk)n*iOC1~mDhASGZ@N{G|dF|Q^@1ljhe=>;wusA&NvY*w%~ zl+R6B^1yZiF)YN>0ms%}qz-^U-HVyiN3R9k1q4)XgDj#qY4CE0)52%evvrrOc898^ z*^)XFR?W%g0@?|6Mxo1ZBp%(XNv_RD-<#b^?-Fs+NL^EUW=iV|+Vy*F%;rBz~pN7%-698U-VMfGEVnmEz7fL1p)-5sLT zL;Iz>FCLM$p$c}g^tbkGK1G$IALq1Gd|We@&TtW!?4C7x4l*=4oF&&sr0Hu`x<5!m zhX&&Iyjr?AkNXU_5P_b^Q3U9sy#f6ZF@2C96$>1k*E-E%DjwvA{VL0PdU~suN~DZo zm{T!>sRdp`Ldpp9olrH@(J$QyGq!?#o1bUo=XP2OEuT3`XzI>s^0P{manUaE4pI%! zclQq;lbT;nx7v3tR9U)G39h?ryrxzd0xq4KX7nO?piJZbzT_CU&O=T(Vt;>jm?MgC z2vUL#*`UcMsx%w#vvjdamHhmN!(y-hr~byCA-*iCD};#l+bq;gkwQ0oN=AyOf@8ow>Pj<*A~2*dyjK}eYdN);%!t1 z6Y=|cuEv-|5BhA?n2Db@4s%y~(%Wse4&JXw=HiO48%c6LB~Z0SL1(k^9y?ax%oj~l zf7(`iAYLdPRq*ztFC z7VtAb@s{as%&Y;&WnyYl+6Wm$ru*u!MKIg_@01od-iQft0rMjIj8e7P9eKvFnx_X5 zd%pDg-|8<>T2Jdqw>AII+fe?CgP+fL(m0&U??QL8YzSjV{SFi^vW~;wN@or_(q<0Y zRt~L}#JRcHOvm$CB)T1;;7U>m%)QYBLTR)KTARw%zoDxgssu5#v{UEVIa<>{8dtkm zXgbCGp$tfue+}#SD-PgiNT{Zu^YA9;4BnM(wZ9-biRo_7pN}=aaimjYgC=;9@g%6< zxol5sT_$<8{LiJ6{l1+sV)Z_QdbsfEAEMw!5*zz6)Yop?T0DMtR_~wfta)E6_G@k# zZRP11D}$ir<`IQ`<(kGfAS?O-DzCyuzBq6dxGTNNTK?r^?zT30mLY!kQ=o~Hv*k^w zvq!LBjW=zzIi%UF@?!g9vt1CqdwV(-2LYy2=E@Z?B}JDyVkluHtzGsWuI1W5svX~K z&?UJ45$R7g>&}SFnLnmw09R2tUgmr_w6mM9C}8GvQX>nL&5R#xBqnp~Se(I>R42`T zqZe9p6G(VzNB3QD><8+y%{e%6)sZDRXTR|MI zM#eZmao-~_`N|>Yf;a;7yvd_auTG#B?Vz5D1AHx=zpVUFe7*hME z+>KH5h1In8hsVhrstc>y0Q!FHR)hzgl+*Q&5hU9BVJlNGRkXiS&06eOBV^dz3;4d5 zeYX%$62dNOprZV$px~#h1RH?_E%oD6y;J;pF%~y8M)8pQ0olYKj6 zE+hd|7oY3ot=j9ZZ))^CCPADL6Jw%)F@A{*coMApcA$7fZ{T@3;WOQ352F~q6`Mgi z$RI6$8)a`Aaxy<8Bc;{wlDA%*%(msBh*xy$L-cBJvQ8hj#FCyT^%+Phw1~PaqyDou^JR0rxDkSrmAdjeYDFDZ`E z)G3>XtpaSPDlydd$RGHg;#4|4{aP5c_Om z2u5xgnhnA)K%8iU==}AxPxZCYC)lyOlj9as#`5hZ=<6<&DB%i_XCnt5=pjh?iusH$ z>)E`@HNZcAG&RW3Ys@`Ci{;8PNzE-ZsPw$~Wa!cP$ye+X6;9ceE}ah+3VY7Mx}#0x zbqYa}eO*FceiY2jNS&2cH9Y}(;U<^^cWC5Ob&)dZedvZA9HewU3R;gRQ)}hUdf+~Q zS_^4ds*W1T#bxS?%RH&<739q*n<6o|mV;*|1s>ly-Biu<2*{!!0#{_234&9byvn0* z5=>{95Zfb{(?h_Jk#ocR$FZ78O*UTOxld~0UF!kyGM|nH%B*qf)Jy}N!uT9NGeM19 z-@=&Y0yGGo_dw!FD>juk%P$6$qJkj}TwLBoefi;N-$9LAeV|)|-ET&culW9Sb_pc_ zp{cXI0>I0Jm_i$nSvGnYeLSSj{ccVS2wyL&0x~&5v;3Itc82 z5lIAkfn~wcY-bQB$G!ufWt%qO;P%&2B_R5UKwYxMemIaFm)qF1rA zc>gEihb=jBtsXCi0T%J37s&kt*3$s7|6)L(%UiY)6axuk{6RWIS8^+u;)6!R?Sgap z9|6<0bx~AgVi|*;zL@2x>Pbt2Bz*uv4x-`{F)XatTs`S>unZ#P^ZiyjpfL_q2z^fqgR-fbOcG=Y$q>ozkw1T6dH8-)&ww+z?E0 zR|rV(9bi6zpX3Ub>PrPK!{X>e$C66qCXAeFm)Y+lX8n2Olt7PNs*1^si)j!QmFV#t z0P2fyf$N^!dyTot&`Ew5{i5u<8D`8U`qs(KqaWq5iOF3x2!-z65-|HsyYz(MAKZ?< zCpQR;E)wn%s|&q(LVm0Ab>gdmCFJeKwVTnv@Js%!At;I=A>h=l=p^&<4;Boc{$@h< z38v`3&2wJtka@M}GS%9!+SpJ}sdtoYzMevVbnH+d_eMxN@~~ zZq@k)7V5f8u!yAX2qF3qjS7g%n$JuGrMhQF!&S^7(%Y{rP*w2FWj(v_J{+Hg*}wdWOd~pHQ19&n3RWeljK9W%sz&Y3Tm3 zR`>6YR54%qBHGa)2xbs`9cs_EsNHxsfraEgZ)?vrtooeA0sPKJK7an){ngtV@{SBa zkO6ORr1_Xqp+`a0e}sC*_y(|RKS13ikmHp3C^XkE@&wjbGWrt^INg^9lDz#B;bHiW zkK4{|cg08b!yHFSgPca5)vF&gqCgeu+c82%&FeM^Bb}GUxLy-zo)}N;#U?sJ2?G2BNe*9u_7kE5JeY!it=f`A_4gV3} z`M!HXZy#gN-wS!HvHRqpCHUmjiM;rVvpkC!voImG%OFVN3k(QG@X%e``VJSJ@Z7tb z*Onlf>z^D+&$0!4`IE$;2-NSO9HQWd+UFW(r;4hh;(j^p4H-~6OE!HQp^96v?{9Zt z;@!ZcccV%C2s6FMP#qvo4kG6C04A>XILt>JW}%0oE&HM5f6 zYLD!;My>CW+j<~=Wzev{aYtx2ZNw|ptTFV(4;9`6Tmbz6K1)fv4qPXa2mtoPt&c?P zhmO+*o8uP3ykL6E$il00@TDf6tOW7fmo?Oz_6GU^+5J=c22bWyuH#aNj!tT-^IHrJ zu{aqTYw@q;&$xDE*_kl50Jb*dp`(-^p={z}`rqECTi~3 z>0~A7L6X)=L5p#~$V}gxazgGT7$3`?a)zen>?TvAuQ+KAIAJ-s_v}O6@`h9n-sZk> z`3{IJeb2qu9w=P*@q>iC`5wea`KxCxrx{>(4{5P+!cPg|pn~;n@DiZ0Y>;k5mnKeS z!LIfT4{Lgd=MeysR5YiQKCeNhUQ;Os1kAymg6R!u?j%LF z4orCszIq_n52ulpes{(QN|zirdtBsc{9^Z72Ycb2ht?G^opkT_#|4$wa9`)8k3ilU z%ntAi`nakS1r10;#k^{-ZGOD&Z2|k=p40hRh5D7(&JG#Cty|ECOvwsSHkkSa)36$4 z?;v#%@D(=Raw(HP5s>#4Bm?f~n1@ebH}2tv#7-0l-i^H#H{PC|F@xeNS+Yw{F-&wH z07)bj8MaE6`|6NoqKM~`4%X> zKFl&7g1$Z3HB>lxn$J`P`6GSb6CE6_^NA1V%=*`5O!zP$a7Vq)IwJAki~XBLf=4TF zPYSL}>4nOGZ`fyHChq)jy-f{PKFp6$plHB2=;|>%Z^%)ecVue(*mf>EH_uO^+_zm? zJATFa9SF~tFwR#&0xO{LLf~@}s_xvCPU8TwIJgBs%FFzjm`u?1699RTui;O$rrR{# z1^MqMl5&6)G%@_k*$U5Kxq84!AdtbZ!@8FslBML}<`(Jr zenXrC6bFJP=R^FMBg7P?Pww-!a%G@kJH_zezKvuWU0>m1uyy}#Vf<$>u?Vzo3}@O% z1JR`B?~Tx2)Oa|{DQ_)y9=oY%haj!80GNHw3~qazgU-{|q+Bl~H94J!a%8UR?XsZ@ z0*ZyQugyru`V9b(0OrJOKISfi89bSVR zQy<+i_1XY}4>|D%X_`IKZUPz6=TDb)t1mC9eg(Z=tv zq@|r37AQM6A%H%GaH3szv1L^ku~H%5_V*fv$UvHl*yN4iaqWa69T2G8J2f3kxc7UE zOia@p0YNu_q-IbT%RwOi*|V|&)e5B-u>4=&n@`|WzH}BK4?33IPpXJg%`b=dr_`hU z8JibW_3&#uIN_#D&hX<)x(__jUT&lIH$!txEC@cXv$7yB&Rgu){M`9a`*PH} zRcU)pMWI2O?x;?hzR{WdzKt^;_pVGJAKKd)F$h;q=Vw$MP1XSd<;Mu;EU5ffyKIg+ z&n-Nb?h-ERN7(fix`htopPIba?0Gd^y(4EHvfF_KU<4RpN0PgVxt%7Yo99X*Pe|zR z?ytK&5qaZ$0KSS$3ZNS$$k}y(2(rCl=cuYZg{9L?KVgs~{?5adxS))Upm?LDo||`H zV)$`FF3icFmxcQshXX*1k*w3O+NjBR-AuE70=UYM*7>t|I-oix=bzDwp2*RoIwBp@r&vZukG; zyi-2zdyWJ3+E?{%?>e2Ivk`fAn&Ho(KhGSVE4C-zxM-!j01b~mTr>J|5={PrZHOgO zw@ND3=z(J7D>&C7aw{zT>GHhL2BmUX0GLt^=31RRPSnjoUO9LYzh_yegyPoAKhAQE z>#~O27dR4&LdQiak6={9_{LN}Z>;kyVYKH^d^*!`JVSXJlx#&r4>VnP$zb{XoTb=> zZsLvh>keP3fkLTIDdpf-@(ADfq4=@X=&n>dyU0%dwD{zsjCWc;r`-e~X$Q3NTz_TJ zOXG|LMQQIjGXY3o5tBm9>k6y<6XNO<=9H@IXF;63rzsC=-VuS*$E{|L_i;lZmHOD< zY92;>4spdeRn4L6pY4oUKZG<~+8U-q7ZvNOtW0i*6Q?H`9#U3M*k#4J;ek(MwF02x zUo1wgq9o6XG#W^mxl>pAD)Ll-V5BNsdVQ&+QS0+K+?H-gIBJ-ccB1=M_hxB6qcf`C zJ?!q!J4`kLhAMry4&a_0}up{CFevcjBl|N(uDM^N5#@&-nQt2>z*U}eJGi}m5f}l|IRVj-Q;a>wcLpK5RRWJ> zysdd$)Nv0tS?b~bw1=gvz3L_ZAIdDDPj)y|bp1;LE`!av!rODs-tlc}J#?erTgXRX z$@ph%*~_wr^bQYHM7<7=Q=45v|Hk7T=mDpW@OwRy3A_v`ou@JX5h!VI*e((v*5Aq3 zVYfB4<&^Dq5%^?~)NcojqK`(VXP$`#w+&VhQOn%;4pCkz;NEH6-FPHTQ+7I&JE1+Ozq-g43AEZV>ceQ^9PCx zZG@OlEF~!Lq@5dttlr%+gNjRyMwJdJU(6W_KpuVnd{3Yle(-p#6erIRc${l&qx$HA z89&sp=rT7MJ=DuTL1<5{)wtUfpPA|Gr6Q2T*=%2RFm@jyo@`@^*{5{lFPgv>84|pv z%y{|cVNz&`9C*cUely>-PRL)lHVErAKPO!NQ3<&l5(>Vp(MuJnrOf^4qpIa!o3D7( z1bjn#Vv$#or|s7Hct5D@%;@48mM%ISY7>7@ft8f?q~{s)@BqGiupoK1BAg?PyaDQ1 z`YT8{0Vz{zBwJ={I4)#ny{RP{K1dqzAaQN_aaFC%Z>OZ|^VhhautjDavGtsQwx@WH zr|1UKk^+X~S*RjCY_HN!=Jx>b6J8`Q(l4y|mc<6jnkHVng^Wk(A13-;AhawATsmmE#H%|8h}f1frs2x@Fwa_|ea+$tdG2Pz{7 z!ox^w^>^Cv4e{Xo7EQ7bxCe8U+LZG<_e$RnR?p3t?s^1Mb!ieB z#@45r*PTc_yjh#P=O8Zogo+>1#|a2nJvhOjIqKK1U&6P)O%5s~M;99O<|Y9zomWTL z666lK^QW`)cXV_^Y05yQZH3IRCW%25BHAM$c0>w`x!jh^15Zp6xYb!LoQ zr+RukTw0X2mxN%K0%=8|JHiaA3pg5+GMfze%9o5^#upx0M?G9$+P^DTx7~qq9$Qoi zV$o)yy zuUq>3c{_q+HA5OhdN*@*RkxRuD>Bi{Ttv_hyaaB;XhB%mJ2Cb{yL;{Zu@l{N?!GKE7es6_9J{9 zO(tmc0ra2;@oC%SS-8|D=omQ$-Dj>S)Utkthh{ovD3I%k}HoranSepC_yco2Q8 zY{tAuPIhD{X`KbhQIr%!t+GeH%L%q&p z3P%<-S0YY2Emjc~Gb?!su85}h_qdu5XN2XJUM}X1k^!GbwuUPT(b$Ez#LkG6KEWQB z7R&IF4srHe$g2R-SB;inW9T{@+W+~wi7VQd?}7||zi!&V^~o0kM^aby7YE_-B63^d zf_uo8#&C77HBautt_YH%v6!Q>H?}(0@4pv>cM6_7dHJ)5JdyV0Phi!)vz}dv{*n;t zf(+#Hdr=f8DbJqbMez)(n>@QT+amJ7g&w6vZ-vG^H1v~aZqG~u!1D(O+jVAG0EQ*aIsr*bsBdbD`)i^FNJ z&B@yxqPFCRGT#}@dmu-{0vp47xk(`xNM6E=7QZ5{tg6}#zFrd8Pb_bFg7XP{FsYP8 zbvWqG6#jfg*4gvY9!gJxJ3l2UjP}+#QMB(*(?Y&Q4PO`EknE&Cb~Yb@lCbk;-KY)n zzbjS~W5KZ3FV%y>S#$9Sqi$FIBCw`GfPDP|G=|y32VV-g@a1D&@%_oAbB@cAUx#aZ zlAPTJ{iz#Qda8(aNZE&0q+8r3&z_Ln)b=5a%U|OEcc3h1f&8?{b8ErEbilrun}mh3 z$1o^$-XzIiH|iGoJA`w`o|?w3m*NX|sd$`Mt+f*!hyJvQ2fS*&!SYn^On-M|pHGlu z4SC5bM7f6BAkUhGuN*w`97LLkbCx=p@K5RL2p>YpDtf{WTD|d3ucb6iVZ-*DRtoEA zCC5(x)&e=giR_id>5bE^l%Mxx>0@FskpCD4oq@%-Fg$8IcdRwkfn;DsjoX(v;mt3d z_4Mnf#Ft4x!bY!7Hz?RRMq9;5FzugD(sbt4up~6j?-or+ch~y_PqrM2hhTToJjR_~ z)E1idgt7EW>G*9%Q^K;o_#uFjX!V2pwfpgi>}J&p_^QlZki!@#dkvR`p?bckC`J*g z=%3PkFT3HAX2Q+dShHUbb1?ZcK8U7oaufLTCB#1W{=~k0Jabgv>q|H+GU=f-y|{p4 zwN|AE+YbCgx=7vlXE?@gkXW9PaqbO#GB=4$o0FkNT#EI?aLVd2(qnPK$Yh%YD%v(mdwn}bgsxyIBI^)tY?&G zi^2JfClZ@4b{xFjyTY?D61w@*ez2@5rWLpG#34id?>>oPg{`4F-l`7Lg@D@Hc}On} zx%BO4MsLYosLGACJ-d?ifZ35r^t*}wde>AAWO*J-X%jvD+gL9`u`r=kP zyeJ%FqqKfz8e_3K(M1RmB?gIYi{W7Z<THP2ihue0mbpu5n(x_l|e1tw(q!#m5lmef6ktqIb${ zV+ee#XRU}_dDDUiV@opHZ@EbQ<9qIZJMDsZDkW0^t3#j`S)G#>N^ZBs8k+FJhAfu< z%u!$%dyP3*_+jUvCf-%{x#MyDAK?#iPfE<(@Q0H7;a125eD%I(+!x1f;Sy`e<9>nm zQH4czZDQmW7^n>jL)@P@aAuAF$;I7JZE5a8~AJI5CNDqyf$gjloKR7C?OPt9yeH}n5 zNF8Vhmd%1O>T4EZD&0%Dt7YWNImmEV{7QF(dy!>q5k>Kh&Xy8hcBMUvVV~Xn8O&%{ z&q=JCYw#KlwM8%cu-rNadu(P~i3bM<_a{3!J*;vZhR6dln6#eW0^0kN)Vv3!bqM`w z{@j*eyzz=743dgFPY`Cx3|>ata;;_hQ3RJd+kU}~p~aphRx`03B>g4*~f%hUV+#D9rYRbsGD?jkB^$3XcgB|3N1L& zrmk9&Dg450mAd=Q_p?gIy5Zx7vRL?*rpNq76_rysFo)z)tp0B;7lSb9G5wX1vC9Lc z5Q8tb-alolVNWFsxO_=12o}X(>@Mwz1mkYh1##(qQwN=7VKz?61kay8A9(94Ky(4V zq6qd2+4a20Z0QRrmp6C?4;%U?@MatfXnkj&U6bP_&2Ny}BF%4{QhNx*Tabik9Y-~Z z@0WV6XD}aI(%pN}oW$X~Qo_R#+1$@J8(31?zM`#e`#(0f<-AZ^={^NgH#lc?oi(Mu zMk|#KR^Q;V@?&(sh5)D;-fu)rx%gXZ1&5)MR+Mhssy+W>V%S|PRNyTAd}74<(#J>H zR(1BfM%eIv0+ngHH6(i`?-%_4!6PpK*0X)79SX0X$`lv_q>9(E2kkkP;?c@rW2E^Q zs<;`9dg|lDMNECFrD3jTM^Mn-C$44}9d9Kc z#>*k&e#25;D^%82^1d@Yt{Y91MbEu0C}-;HR4+IaCeZ`l?)Q8M2~&E^FvJ?EBJJ(% zz1>tCW-E~FB}DI}z#+fUo+=kQME^=eH>^%V8w)dh*ugPFdhMUi3R2Cg}Zak4!k_8YW(JcR-)hY8C zXja}R7@%Q0&IzQTk@M|)2ViZDNCDRLNI)*lH%SDa^2TG4;%jE4n`8`aQAA$0SPH2@ z)2eWZuP26+uGq+m8F0fZn)X^|bNe z#f{qYZS!(CdBdM$N2(JH_a^b#R2=>yVf%JI_ieRFB{w&|o9txwMrVxv+n78*aXFGb z>Rkj2yq-ED<)A46T9CL^$iPynv`FoEhUM10@J+UZ@+*@_gyboQ>HY9CiwTUo7OM=w zd~$N)1@6U8H#Zu(wGLa_(Esx%h@*pmm5Y9OX@CY`3kPYPQx@z8yAgtm(+agDU%4?c zy8pR4SYbu8vY?JX6HgVq7|f=?w(%`m-C+a@E{euXo>XrGmkmFGzktI*rj*8D z)O|CHKXEzH{~iS+6)%ybRD|JRQ6j<+u_+=SgnJP%K+4$st+~XCVcAjI9e5`RYq$n{ zzy!X9Nv7>T4}}BZpSj9G9|(4ei-}Du<_IZw+CB`?fd$w^;=j8?vlp(#JOWiHaXJjB0Q00RHJ@sG6N#y^H7t^&V} z;VrDI4?75G$q5W9mV=J2iP24NHJy&d|HWHva>FaS#3AO?+ohh1__FMx;?`f{HG3v0 ztiO^Wanb>U4m9eLhoc_2B(ca@YdnHMB*~aYO+AE(&qh@?WukLbf_y z>*3?Xt-lxr?#}y%kTv+l8;!q?Hq8XSU+1E8x~o@9$)zO2z9K#(t`vPDri`mKhv|sh z{KREcy`#pnV>cTT7dm7M9B@9qJRt3lfo(C`CNkIq@>|2<(yn!AmVN?ST zbX_`JjtWa3&N*U{K7FYX8})*D#2@KBae` zhKS~s!r%SrXdhCsv~sF}7?ocyS?afya6%rDBu6g^b2j#TOGp^1zrMR}|70Z>CeYq- z1o|-=FBKlu{@;pm@QQJ_^!&hzi;0Z_Ho){x3O1KQ#TYk=rAt9`YKC0Y^}8GWIN{QW znYJyVTrmNvl!L=YS1G8BAxGmMUPi+Q7yb0XfG`l+L1NQVSbe^BICYrD;^(rke{jWCEZOtVv3xFze!=Z&(7}!)EcN;v0Dbit?RJ6bOr;N$ z=nk8}H<kCEE+IK3z<+3mkn4q!O7TMWpKShWWWM)X*)m6k%3luF6c>zOsFccvfLWf zH+mNkh!H@vR#~oe=ek}W3!71z$Dlj0c(%S|sJr>rvw!x;oCek+8f8s!U{DmfHcNpO z9>(IKOMfJwv?ey`V2ysSx2Npeh_x#bMh)Ngdj$al;5~R7Ac5R2?*f{hI|?{*$0qU- zY$6}ME%OGh^zA^z9zJUs-?a4ni8cw_{cYED*8x{bWg!Fn9)n;E9@B+t;#k}-2_j@# zg#b%R(5_SJAOtfgFCBZc`n<&z6)%nOIu@*yo!a% zpLg#36KBN$01W{b;qWN`Tp(T#jh%;Zp_zpS64lvBVY2B#UK)p`B4Oo)IO3Z&D6<3S zfF?ZdeNEnzE{}#gyuv)>;z6V{!#bx)` zY;hL*f(WVD*D9A4$WbRKF2vf;MoZVdhfWbWhr{+Db5@M^A4wrFReuWWimA4qp`GgoL2`W4WPUL5A=y3Y3P z%G?8lLUhqo@wJW8VDT`j&%YY7xh51NpVYlsrk_i4J|pLO(}(b8_>%U2M`$iVRDc-n zQiOdJbroQ%*vhN{!{pL~N|cfGooK_jTJCA3g_qs4c#6a&_{&$OoSQr_+-O^mKP=Fu zGObEx`7Qyu{nHTGNj(XSX*NPtAILL(0%8Jh)dQh+rtra({;{W2=f4W?Qr3qHi*G6B zOEj7%nw^sPy^@05$lOCjAI)?%B%&#cZ~nC|=g1r!9W@C8T0iUc%T*ne z)&u$n>Ue3FN|hv+VtA+WW)odO-sdtDcHfJ7s&|YCPfWaVHpTGN46V7Lx@feE#Od%0XwiZy40plD%{xl+K04*se zw@X4&*si2Z_0+FU&1AstR)7!Th(fdaOlsWh`d!y=+3m!QC$Zlkg8gnz!}_B7`+wSz z&kD?6{zPnE3uo~Tv8mLP%RaNt2hcCJBq=0T>%MW~Q@Tpt2pPP1?KcywH>in5@ zx+5;xu-ltFfo5vLU;2>r$-KCHjwGR&1XZ0YNyrXXAUK!FLM_7mV&^;;X^*YH(FLRr z`0Jjg7wiq2bisa`CG%o9i)o1`uG?oFjU_Zrv1S^ipz$G-lc^X@~6*)#%nn+RbgksJfl{w=k31(q>7a!PCMp5YY{+Neh~mo zG-3dd!0cy`F!nWR?=9f_KP$X?Lz&cLGm_ohy-|u!VhS1HG~e7~xKpYOh=GmiiU;nu zrZ5tWfan3kp-q_vO)}vY6a$19Q6UL0r znJ+iSHN-&w@vDEZ0V%~?(XBr|jz&vrBNLOngULxtH(Rp&U*rMY42n;05F11xh?k;n_DX2$4|vWIkXnbwfC z=ReH=(O~a;VEgVO?>qsP*#eOC9Y<_9Yt<6X}X{PyF7UXIA$f)>NR5P&4G_Ygq(9TwwQH*P>Rq>3T4I+t2X(b5ogXBAfNf!xiF#Gilm zp2h{&D4k!SkKz-SBa%F-ZoVN$7GX2o=(>vkE^j)BDSGXw?^%RS9F)d_4}PN+6MlI8*Uk7a28CZ)Gp*EK)`n5i z){aq=0SFSO-;sw$nAvJU-$S-cW?RSc7kjEBvWDr1zxb1J7i;!i+3PQwb=)www?7TZ zE~~u)vO>#55eLZW;)F(f0KFf8@$p)~llV{nO7K_Nq-+S^h%QV_CnXLi)p*Pq&`s!d zK2msiR;Hk_rO8`kqe_jfTmmv|$MMo0ll}mI)PO4!ikVd(ZThhi&4ZwK?tD-}noj}v zBJ?jH-%VS|=t)HuTk?J1XaDUjd_5p1kPZi6y#F6$lLeRQbj4hsr=hX z4tXkX2d5DeLMcAYTeYm|u(XvG5JpW}hcOs4#s8g#ihK%@hVz|kL=nfiBqJ{*E*WhC zht3mi$P3a(O5JiDq$Syu9p^HY&9~<#H89D8 zJm84@%TaL_BZ+qy8+T3_pG7Q%z80hnjN;j>S=&WZWF48PDD%55lVuC0%#r5(+S;WH zS7!HEzmn~)Ih`gE`faPRjPe^t%g=F ztpGVW=Cj5ZkpghCf~`ar0+j@A=?3(j@7*pq?|9)n*B4EQTA1xj<+|(Y72?m7F%&&& zdO44owDBPT(8~RO=dT-K4#Ja@^4_0v$O3kn73p6$s?mCmVDUZ+Xl@QcpR6R3B$=am z%>`r9r2Z79Q#RNK?>~lwk^nQlR=Hr-ji$Ss3ltbmB)x@0{VzHL-rxVO(++@Yr@Iu2 zTEX)_9sVM>cX$|xuqz~Y8F-(n;KLAfi*63M7mh&gsPR>N0pd9h!0bm%nA?Lr zS#iEmG|wQd^BSDMk0k?G>S-uE$vtKEF8Dq}%vLD07zK4RLoS?%F1^oZZI$0W->7Z# z?v&|a`u#UD=_>i~`kzBGaPj!mYX5g?3RC4$5EV*j0sV)>H#+$G6!ci=6`)85LWR=FCp-NUff`;2zG9nU6F~ z;3ZyE*>*LvUgae+uMf}aV}V*?DCM>{o31+Sx~6+sz;TI(VmIpDrN3z+BUj`oGGgLP z>h9~MP}Pw#YwzfGP8wSkz`V#}--6}7S9yZvb{;SX?6PM_KuYpbi~*=teZr-ga2QqIz{QrEyZ@>eN*qmy;N@FCBbRNEeeoTmQyrX;+ zCkaJ&vOIbc^2BD6_H+Mrcl?Nt7O{xz9R_L0ZPV_u!sz+TKbXmhK)0QWoe-_HwtKJ@@7=L+ z+K8hhf=4vbdg3GqGN<;v-SMIzvX=Z`WUa_91Yf89^#`G(f-Eq>odB^p-Eqx}ENk#&MxJ+%~Ad2-*`1LNT>2INPw?*V3&kE;tt?rQyBw? zI+xJD04GTz1$7~KMnfpkPRW>f%n|0YCML@ODe`10;^DXX-|Hb*IE%_Vi#Pn9@#ufA z_8NY*1U%VseqYrSm?%>F@`laz+f?+2cIE4Jg6 z_VTcx|DSEA`g!R%RS$2dSRM|9VQClsW-G<~=j5T`pTbu-x6O`R z98b;}`rPM(2={YiytrqX+uh65f?%XiPp`;4CcMT*E*dQJ+if9^D>c_Dk8A(cE<#r=&!& z_`Z01=&MEE+2@yr!|#El=yM}v>i=?w^2E_FLPy(*4A9XmCNy>cBWdx3U>1RylsItO z4V8T$z3W-qqq*H`@}lYpfh=>C!tieKhoMGUi)EpWDr;yIL&fy};Y&l|)f^QE*k~4C zH>y`Iu%#S)z)YUqWO%el*Z)ME#p{1_8-^~6UF;kBTW zMQ!eXQuzkR#}j{qb(y9^Y!X7&T}}-4$%4w@w=;w+>Z%uifR9OoQ>P?0d9xpcwa>7kTv2U zT-F?3`Q`7xOR!gS@j>7In>_h){j#@@(ynYh;nB~}+N6qO(JO1xA z@59Pxc#&I~I64slNR?#hB-4XE>EFU@lUB*D)tu%uEa))B#eJ@ZOX0hIulfnDQz-y8 z`CX@(O%_VC{Ogh&ot``jlDL%R!f>-8yq~oLGxBO?+tQb5%k@a9zTs!+=NOwSVH-cR zqFo^jHeXDA_!rx$NzdP;>{-j5w3QUrR<;}=u2|FBJ;D#v{SK@Z6mjeV7_kFmWt95$ zeGaF{IU?U>?W`jzrG_9=9}yN*LKyzz))PLE+)_jc#4Rd$yFGol;NIk(qO1$5VXR)+ zxF7%f4=Q!NzR>DVXUB&nUT&>Nyf+5QRF+Z`X-bB*7=`|Go5D1&h~ zflKLw??kpiRm0h3|1GvySC2^#kcFz^5{79KKlq@`(leBa=_4CgV9sSHr{RIJ^KwR_ zY??M}-x^=MD+9`v@I3jue=OCn0kxno#6i>b(XKk_XTp_LpI}X*UA<#* zsgvq@yKTe_dTh>q1aeae@8yur08S(Q^8kXkP_ty48V$pX#y9)FQa~E7P7}GP_CbCm zc2dQxTeW(-~Y6}im24*XOC8ySfH*HMEnW3 z4CXp8iK(Nk<^D$g0kUW`8PXn2kdcDk-H@P0?G8?|YVlIFb?a>QunCx%B9TzsqQQ~HD!UO7zq^V!v9jho_FUob&Hxi ztU1nNOK)a!gkb-K4V^QVX05*>-^i|{b`hhvQLyj`E1vAnj0fbqqO%r z6Q;X1x0dL~GqMv%8QindZ4CZ%7pYQW~ z9)I*#Gjref-q(4Z*E#1c&rE0-_(4;_M(V7rgH_7H;ps1s%GBmU z{4a|X##j#XUF2n({v?ZUUAP5k>+)^F)7n-npbV3jAlY8V3*W=fwroDS$c&r$>8aH` zH+irV{RG3^F3oW2&E%5hXgMH9>$WlqX76Cm+iFmFC-DToTa`AcuN9S!SB+BT-IA#3P)JW1m~Cuwjs`Ep(wDXE4oYmt*aU z!Naz^lM}B)JFp7ejro7MU9#cI>wUoi{lylR2~s)3M!6a=_W~ITXCPd@U9W)qA5(mdOf zd3PntGPJyRX<9cgX?(9~TZB5FdEHW~gkJXY51}?s4ZT_VEdwOwD{T2E-B>oC8|_ZwsPNj=-q(-kwy%xX2K0~H z{*+W`-)V`7@c#Iuaef=?RR2O&x>W0A^xSwh5MsjTz(DVG-EoD@asu<>72A_h<39_# zawWVU<9t{r*e^u-5Q#SUI6dV#p$NYEGyiowT>>d*or=Ps!H$-3={bB|An$GPkP5F1 zTnu=ktmF|6E*>ZQvk^~DX(k!N`tiLut*?3FZhs$NUEa4ccDw66-~P;x+0b|<!ZN7Z%A`>2tN#CdoG>((QR~IV_Gj^Yh%!HdA~4C3jOXaqb6Ou z21T~Wmi9F6(_K0@KR@JDTh3-4mv2=T7&ML<+$4;b9SAtv*Uu`0>;VVZHB{4?aIl3J zL(rMfk?1V@l)fy{J5DhVlj&cWKJCcrpOAad(7mC6#%|Sn$VwMjtx6RDx1zbQ|Ngg8N&B56DGhu;dYg$Z{=YmCNn+?ceDclp65c_RnKs4*vefnhudSlrCy6-96vSB4_sFAj# zftzECwmNEOtED^NUt{ZDjT7^g>k1w<=af>+0)%NA;IPq6qx&ya7+QAu=pk8t>KTm` zEBj9J*2t|-(h)xc>Us*jHs)w9qmA>8@u21UqzKk*Ei#0kCeW6o z-2Q+Tvt25IUkb}-_LgD1_FUJ!U8@8OC^9(~Kd*0#zr*8IQkD)6Keb(XFai5*DYf~` z@U?-{)9X&BTf!^&@^rjmvea#9OE~m(D>qfM?CFT9Q4RxqhO0sA7S)=--^*Q=kNh7Y zq%2mu_d_#23d`+v`Ol263CZ<;D%D8Njj6L4T`S*^{!lPL@pXSm>2;~Da- zBX97TS{}exvSva@J5FJVCM$j4WDQuME`vTw>PWS0!;J7R+Kq zVUy6%#n5f7EV(}J#FhDpts;>=d6ow!yhJj8j>MJ@Wr_?x30buuutIG97L1A*QFT$c ziC5rBS;#qj=~yP-yWm-p(?llTwDuhS^f&<(9vA9@UhMH2-Fe_YAG$NvK6X{!mvPK~ zuEA&PA}meylmaIbbJXDOzuIn8cJNCV{tUA<$Vb?57JyAM`*GpEfMmFq>)6$E(9e1@W`l|R%-&}38#bl~levA#fx2wiBk^)mPj?<=S&|gv zQO)4*91$n08@W%2b|QxEiO0KxABAZC{^4BX^6r>Jm?{!`ZId9jjz<%pl(G5l));*`UU3KfnuXSDj2aP>{ zRIB$9pm7lj3*Xg)c1eG!cb+XGt&#?7yJ@C)(Ik)^OZ5><4u$VLCqZ#q2NMCt5 z6$|VN(RWM;5!JV?-h<JkEZ(SZF zC(6J+>A6Am9H7OlOFq6S62-2&z^Np=#xXsOq0WUKr zY_+Ob|CQd1*!Hirj5rn*=_bM5_zKmq6lG zn*&_=x%?ATxZ8ZTzd%biKY_qyNC#ZQ1vX+vc48N>aJXEjs{Y*3Op`Q7-oz8jyAh>d zNt_qvn`>q9aO~7xm{z`ree%lJ3YHCyC`q`-jUVCn*&NIml!uuMNm|~u3#AV?6kC+B z?qrT?xu2^mobSlzb&m(8jttB^je0mx;TT8}`_w(F11IKz83NLj@OmYDpCU^u?fD{) z&=$ptwVw#uohPb2_PrFX;X^I=MVXPDpqTuYhRa>f-=wy$y3)40-;#EUDYB1~V9t%$ z^^<7Zbs0{eB93Pcy)96%XsAi2^k`Gmnypd-&x4v9rAq<>a(pG|J#+Q>E$FvMLmy7T z5_06W=*ASUyPRfgCeiPIe{b47Hjqpb`9Xyl@$6*ntH@SV^bgH&Fk3L9L=6VQb)Uqa z33u#>ecDo&bK(h1WqSH)b_Th#Tvk&%$NXC@_pg5f-Ma#7q;&0QgtsFO~`V&{1b zbSP*X)jgLtd@9XdZ#2_BX4{X~pS8okF7c1xUhEV9>PZco>W-qz7YMD`+kCGULdK|^ zE7VwQ-at{%&fv`a+b&h`TjzxsyQX05UB~a0cuU-}{*%jR48J+yGWyl3Kdz5}U>;lE zgkba*yI5>xqIPz*Y!-P$#_mhHB!0Fpnv{$k-$xxjLAc`XdmHd1k$V@2QlblfJPrly z*~-4HVCq+?9vha>&I6aRGyq2VUon^L1a)g`-Xm*@bl2|hi2b|UmVYW|b+Gy?!aS-p z86a}Jep6Mf>>}n^*Oca@Xz}kxh)Y&pX$^CFAmi#$YVf57X^}uQD!IQSN&int=D> zJ>_|au3Be?hmPKK)1^JQ(O29eTf`>-x^jF2xYK6j_9d_qFkWHIan5=7EmDvZoQWz5 zZGb<{szHc9Nf@om)K_<=FuLR<&?5RKo3LONFQZ@?dyjemAe4$yDrnD zglU#XYo6|~L+YpF#?deK6S{8A*Ou;9G`cdC4S0U74EW18bc5~4>)<*}?Z!1Y)j;Ot zosEP!pc$O^wud(={WG%hY07IE^SwS-fGbvpP?;l8>H$;}urY2JF$u#$q}E*ZG%fR# z`p{xslcvG)kBS~B*^z6zVT@e}imYcz_8PRzM4GS52#ms5Jg9z~ME+uke`(Tq1w3_6 zxUa{HerS7!Wq&y(<9yyN@P^PrQT+6ij_qW3^Q)I53iIFCJE?MVyGLID!f?QHUi1tq z0)RNIMGO$2>S%3MlBc09l!6_(ECxXTU>$KjWdZX^3R~@3!SB zah5Za2$63;#y!Y}(wg1#shMePQTzfQfXyJ-Tf`R05KYcyvo8UW9-IWGWnzxR6Vj8_la;*-z5vWuwUe7@sKr#Tr51d z2PWn5h@|?QU3>k=s{pZ9+(}oye zc*95N_iLmtmu}H-t$smi49Y&ovX}@mKYt2*?C-i3Lh4*#q5YDg1Mh`j9ovRDf9&& zp_UMQh`|pC!|=}1uWoMK5RAjdTg3pXPCsYmRkWW}^m&)u-*c_st~gcss(`haA)xVw zAf=;s>$`Gq_`A}^MjY_BnCjktBNHY1*gzh(i0BFZ{Vg^F?Pbf`8_clvdZ)5(J4EWzAP}Ba5zX=S(2{gDugTQ3`%!q`h7kYSnwC`zEWeuFlODKiityMaM9u{Z%E@@y1jmZA#ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z zKpxF`G*X(YOz2^@7i#D+s-~A1E;3&x%%qL5hkiy^JhYjJ74{hvVmAx*6BH`M`!qGC zO9pjEsR)A-n1`6KLACSL%FS_Kcm+?4*z-V?WAZPs?RkzoijIr~I+oh1^~T`q^dCFvG$Gbd8AnTYBjLKYUmayaQz#S1le7Q^Hyr#;X&h*1wDpm+gZC!rSKom zq|+o&UGpeXtlQ1;?@JukKG!8PGS1Io0z6O}ZeL&DsON^I0K+>Mxv#ohK+;ByAZ`Eb z2orY{j0Pa3edA(#-pJA0AaJ6h& z81Gl(pd#j~mrizktoid14K5ig7u8FvZmLLP%l@dl05IprCyqDB?mA2fc*6UB+49lb zZ8`V9epdo=OeZoiY%zw-w`8DNwTORV_>>3T{r)1-YsGSo0E2s>tix9OBqKFBjg#}G z`pgkCblKMYs!Z)r^(qT_c+}gLhR|gnq!1~Qr|~kt&2@_yswx{i$KEn`8J1W8BGljl zr@GEG#W(s#AKKyuqLp+cl1C}7%`m#-!$15XF{M(M*-fD%+i#mFbP35jlgN3{8#A-dmj&OQtG)!031jTwGMal=&YtPfq2AUWekP9J-JT(p099!L`+yen$ zVH1?kRrhV7(mGKkm_jPP_U@Xd;x=ppk}4WY0Rbr> z0MJM_;$GGxL*P68y%KBqHntF{>X&<{aeI4m6+{TQ%~Zp}v%Pujr)zg5mV;cFKqeA- zQm5`#Sd{B6Rc*4PS-rO(vf>YEdXmOK?>K@`L5}|9q}#t_IE%g+U<-1qw3mr5&v;2A zCQ}BEn9_u;;>n5N#dP0RhCF-_UplC+U(i~Zjh>U5+b8%@p3HK(R*IMQwE!uritb}< zF)AK2?+0@-aE3LYkg`B*&N&m~JWB9>(Z>`aqRwgioU)0w{U1K4?>-#i|ZfhNa9hV)2)(%ch zJMH1twoeZWwkE@I!dz$ma+;9GeACv>Ncupl@+gBSeU_uzfj!$+h&@EACkZG_vwLGA z(?^;rcJu1$5H~xI@6lHIYC-$+b&hF1p`AoAOKqw{t0Fu#X`OGt$)7Q!nmJ=&)xjq@ zHoxT4pcYKSPT5(4yzIuQ^S*N2NJpR4v0?rB-^JuaXNLis?E(l>Jo8mUw(gsFLLOy? zEszHWGaCn|lw$LSwoj{G7Uq(zK0W^VVWu#ms8BMRlF2z%-g`fOXmndgC(na8fc)s` zz$GAoxP+l|+T_S4$r1sLwkV77ew1Gug*`|HiE*?FGLm1q; z^p0A0eqqbmk3?|!CB9DBN1Zof6d7+ zJSn!`VD~tVaqy<*Mw^8dM5v3Bvj2VdVFb=)U3L2eDM3@>n(P z?Rr_=I17+r4fE{>1LBQG0&o97nef67n-aNnVP<{dd6*B!Q344 zZbsAof&jw+;CLeK2d87t9s~YZ5?6Qwf&{NPEBN+)LbjOcZRXNcR&h)x`TtdpI+b!>$E~h0o1L*2OddpR9!Gw~-E^Cj(7i69S<66ak$)AYMv|xG+;uR(`;h zGIV3}?+Qxdjz)s;s}jHY{JPmeo@-tN$H@hxaV@)}K?y~ts~E6H(F|SlsN5oH8g7*h zGiC!8c1doE3U|D}Vul1yPmXuCk*hmyU4MG2ml#V0+(G5I+`L_=3cD$%$I=@*8m-LU-!fn&-sZO1%ls63+w}AiAK`Jv z>`q~ztr&&(gCkFpci+*1Ekdv*MhBCzGfPBj9dM|YEjZk(tWBuz4?MGeq+*)t>Q=z6UXF_w z{QDUT4^JQ8J%hW;d2xGB>Fl4Y-bRT!ttP2GE5jYoI1e(eVK0&V5W+>zludt=nf|UN zi1IV;MK$Fy%$yw<oGeW?JIGjmfGLH$Y;l|T0p1V!N*Jvu zHSAG0WpwPip0vm7%VRq8$2O2>P5b!WBfTz*6dZ4Wd6O9Y(8A;nOuG((y?F`ac_u2( z#~17CoTK)1G<~~Z4jXlout{e&nZbDHyHf(=a?OtaJ(2Q(!g#)Ugw-QQ?A?mN#yN%T zBtJ`sA6Lpg`k>Pi8a7GssiY$eG0Be8LCoQL{GDqi-;j0pLmT!Z)szldvbN7GVcu*S zzb1rEq|M)1qa7rM*I8!<#w7FnQ?{v^? z0`MlS3+`#ZB5$DT4+`7e-Hlp_2G0`*F@STbRJ|!tk3cC~1T%NR-p4s=sTT+RqsMjF zyrp-Jv?CD4Y3N&Zb1gr=%`MFR8;|r)uxQ6*X{OpEhQ~+tu}^n8Wijiy`pSMw0uKNi zSNX^Z1y;WirM0o_x%zft0U2GcLm_2BS`b{Z>g|9VOVr%QF*R?pTpiJsEbj4jLVAyd zTA;x15=f~b0^(e*Vo;Tn;WTJSxpI9LmL($Lxob<^S!k7mGhnnVNnAC*g!$ms0#Q|q zs=25I0<>fUw_&+KU`}5P9wlmjRWdMYh%Np6n?AAHQ;JzG?s(Z9UR`pNh79Nzk~DF+ zX~jy>>f-2bl?drlM8 z3NfIQnrT@pLmv+QA6efWPv!sqe;mh3_RcOj5>Ya;4hhN13dtx*_TJ-=kX_kZQDkPz zIw}#e_dK%au@1*L&iUP^cfH?zf1iK)tHv=t|>-9mMT!;;Vg|svSzWkN7q#t$c4N$Q;tl3EYwef_4q>GO<#I89VhY;`X*hz$n*GZ%f+;uViG z?uLlxD1OIeid}0r9%Ssoc7@vJjZIsZlU9zvYpjhYiOrzD5sq3OC zpf-X;Nb!DLpxqX^zDIK%=46-Z3%i-bac`RIBS5*wcw5Pu>G|kF>TQP$dGRYh#1hwD z{|cbbTOKL>Gb1-;X6?vWLC+KJ_^Ij?KzJ7eZ?^8XNgoYU9^z&>d zsIjX*uOK`#Wu!`>L@y!=XpQcW+mBaRjm|XrB@etLdr}Ob57e7EkE;7a*t7=M#XFL6 za;KHHk-rBNTjp-gS^;ehKNv>K>+_jPQ45J%4><1HyKJ?;T9#~k_23?xD}B&@Wp{%H z($hU+nWR?g!9dsJkgVz(J_Yrdns+m~9V_gQ7Sb`&F4wZZ!k}##j$>O{4{?avCbCZfyW zO$)m7LE=P?$CXHDU_RUD+sYwT;nKI7 zSs_XTv!BuxpJ!7(b~uYfsgzt~mj5(vf2r~`LHwpePs!o2A3zEr@#sxo8HEe8>V||d zBiz0@e&6}p*}!6jsm}I0bN9Mc2(c#jg@;Nu6!Kv&4&P8-UcQ-00WJIO%4OuUn;^jU z;I3r=T3KQtiMQ7&x32eVtB`mCe)9ws^7u%2P`B%Xc}=Qc&O^{FmS^{~Rho}^s`B+H z=1_T);9LRK?{$Vx22!5m)Er8aoPOA8&{7fyt`t@~Vw%gtx~+g3qs8LFR%(2Uny28A6dFYnNQgcUa>Sq=%alFh&8#@1o_qgwve* zVFimnUtL{4aHP6s?FB%bu2SP=e*VGqXC8iuZ-JOc{5%Lx0g|VvyWkdh&FD^Gkc!0N zhoolXvp6GC8wj?Y+V;r*EN+<1ac`-+!8Mqb@Nz)=OqV?4gxhR^t7*+^+AfxxVt(n{ z+fkk|-xSGqmkZa@Q%`;;r`-Z|? z0fR6b@l%pTwK*@xY+(MwBUwf^z+F*~piC64BWTrz}-HS1-XF-IA%?Zs_#F8 zcmUuEZ6Of>YIJOe$&{V;3vIBw7|jSGPeS6cvTMdj96Y~pI-z7InGW;(DhFqaiTTO9@KWvQi9__j0btLZ9 zAa~-Po%^sDFfme4@Yiq}r`BgnYK2eTwCjg9_zC4V{{&_GTm-!qHGVR6JXDjw;}GzF z6lXA{xo1+tQM{9vwb1&sRXPdGDHbEMbnwh}t+%tvcw5p4J4r#hEpDl=A{;Mjc%0)T zsG}v<$^HhdcE)5IJ^iBWK{7?Zn)vb%c!5eIj4 zbT}CGO*u)Od@^LuIC@_2{=AP2-O99NglFudj{!T}0e8wtTQcB@F9QW6$J!0Ye`T+U zXDx84b$!hD#4YzSyZLy~!IIZuFa3%eU zG4eg5?}sZ6Yj29P^-PcXG*8%VzLL$0!oL?c(!oQ+G!kORsa+lsf5YER>PX83R4LgF zgPNQJ#Bo#)MXU%J9k?RWD;c>|as5b5p>xAwau=X5XbERX`_ZHB8_XSNDe`s?n(e>) zGF$G%n6o+W{6A-@4hsIK0*J%jpB#Y*G^B48eQD(CDZR5oBl-P=)r7fH^PLf?!aK6V zwkIM35?l*I6p@;^H}JIDNs-fF*IFN?k?kj(M)QKM%%?dSkf1d$Nly2z(>)oq8z}0H zH?Qa{x&36#W@y04!9zx@x7un@ob$&)V8#f~0n1|jF0kFs4aZ{ND1~QjWHToIY5)LY zrgKDCj@dFCx&-w$QMi=CqD*=`$NqC~2k366pPXl#>Y7A=iQD}f`)+B-pS@LIW_M?9 zlBS_)(vGz!L$#P`?<3Hvonw@B1uJ244y)M?0)z0-hq++sJ0GZ+{oiiH;lFi&wy(C! z0Bv9z^M;`4@)USP)7dhg@K5K&U&|7&-@I0Sk>I+ZH75_xEn>qh9qmc%aA@NEKBsVBgUuK zC=b{w-0oU|)~tAVI zyJ3BAB}%rsjz7qZ?x_XCWe6!_u-{e_3u68Asso0IvwKdxq1lN#%4w>J zi>}P;$JZ>58(ZAjsmSJl6BWUTe`0eGEf3f_yS#H6vx;UJWO7CCK!{)4C}`C$j5gNj|k znb$4QRurEE3tPEe!JzG-a0DmvXePO zSD#Q-qOAjTMm|=aBSnvwHoEbgyVIz@J$hT*legak-hhb}e#%cm2$nR2 zV9A{kc)WT$np=5coPQIskbGMO@Fn2NxPv$@SJZdG6}jV;+%(cH+*RFQ(+DjsJlman zy`D(yN?8MCtjWD3w}Q|jQccb$}BDW%M$zZZnri2+5ls)@@(wQD`jt_GpTKL_^CO&SSCcHbfMX#JXYFI^*947 zPh&S-G=l*C@`E5CU1$m7ao(Q&oSmY7)ZZ#5_fEyYzLsFJwJ%GfErFeRN@7lUbUrL| z$6;gQSNsI91LJvT+$Zb0>g<4g8T{B!U05lfKmoSRH^pB^^8sJ3{8PzVq0NeypMF5k zU3qOqksdq{>AUjm3O~dZx^vS6C$ldgCWszl?xd8-sJ;-kPnISB*-f=L*8XggOx$?u zg%B-QovSjBbj}%sShZv~r?`*6PiiQW;nee<-=+y4}S#}q_BgXIJoSOf$YbE7vXt4;Np zrKzZf6Ny0aES8(-cqmnIGMg&ieYWryBZ0VTB=4<*@auP4NdIk&q(Mt(OLPm|Yl za!0OpC9sA#tk>OsaCSx0;!$5r6naw ztzLBo>#LKaxxsO=yWe%yGilL`A|6E#TK! z+1VRQlo*D?(k0-mlRM+`OMT8kVB*-%ZGv}Aj1u^j!wu*~>L<-T+u?6sX!3C}lQte- zk(6_=iwXsQ0JbRvJDwMnk!c99w~s~uD_4vMB=m~-ft-*|z~$*g4g;pgG~Ap1m@@Fx zWS)8IKSN6`^vVQ8hv^Oc+O(Rt7!U%wVsGP+Y6fyS%GG+v+dIdVfCXPzAV~~li+3m5 ztFQmbE)(#2#Oi@k$1#zUS6ijD_yYsa{+BHZAw+^zAEI3bc(h0qm?|pNf?oS}Km#OG zrOfCKn_-CVO;}DXu|5YE#d8I2o>}vUxYlv&>=+I28WY>a1;uI)HUM_IvpF;Ln4ROT zf!=1rpKihNFUo=R@sD-pT!EOm%%ncl43f;aem^;|A#s3`b6vjeAzO!M-gwc`-Kj~{ zBX)tq64*kJl#TrgW4o%hTY3x$P01nD6a6s2#MmwM$vyX5PU|YngU*wXGK*?f?#Eg$~^OWW3I@of-=XVuu-b%A1Z|nqY_2 z;~jD&=QnB#WGU>;RwFq(I< z34K1fCMwf9F}G%k(&?~2EY&)W*-_z0ReS$;7+I1)zz`)M zpAF{5ZHLPMJhYU z;GE*@hM1NM{G{L94dL$!Y-h6A9K9W=I6AYb`Y=v{(tpyLQz^^Aibea(q()R*TU|-m zozpyr!|-BZ_Dn+$*2|vq2Y@ghHo!-`WjVtU-bab(SJp2*2i-}$UP9^qnF_OIFS~-< zYj^VS!)Wu}vn6!LDIt!HJ1SU-@ce>z8f4cT4R9V@O^Xg9)4`VpjsXm*~@%l^Ux;Rf#Zck`BNXu0Y(!C zj%Z}UAmD00nsOS%Uull)dU(fZgJ$bo>3Oa`8h~Wt)EM?v(ndlTS1p0|E9Pg>=&>58 zghD~%R;YpqZAw;F;M(lx5b_wkVbnd+ER+6A-SYj^1XUgNGn0I~ES|f|5emjyPIW)S z0z8i6)BZt&h(qQxih4HbFYa6~jyeKbc_`QEdLD@9SBGButjw|b^l*oQjDk<7Nig08IK zb`ATVGzK%LP+>9aFM0hr8t+m`uNr?h&8o3Rp$T&ql||K}7GgobFhCViaDH~+F#yC- zt>7T3&_PZ*feTKTyd6vlF~JmEA1f+*>CCE4ex}5N^$4o)YuxX&3T$P0(IS!+kan^J z_p>v#1J8bWELml|S02YAQe-&yVew+kipZr~H-I@yc$=8#rZ-8L<_nDx&Qv3dJDwUX z!)@=h1`~R2M{$J8bM^1O&Gy2oxe1T;K?NA{iv_eYuhpLyc3%xu%z`dVc}Z}%cHGHQ<7P!Q|e?dwnSpL!AUf!B^!?#^Q#W!Ry+7ofwPZ1mZq z(Id0{htmX1W?2cAYWZo_lOtT#+Us-nlP$=CGK|Ri4x0Xh>(|iN9y1 z=9y26A4Y}ViRi9Fxzm{>J`YM>GX1D|$4BY9xJrY{oY2~Z&};B{Zq9Pp!pox`8e#0C z-h~@fohA74(#ws!{7kIe4v6XUX<)9bd)g66Bz%^Y4p0~OF+rY;l$v&7T<3~4y!bv> zR$r#LblZcVgy2lq!ff+>yuR4qCcljQa03x|dTcG7`CHcxh#POtGKt6ymNd_0qF7Wf zBj_KC8{jl!zZ>0neDp19n3sD?HC=|WM3!}cK4zCnu6Uoj*hbV1<#F2BD)@A~y%@VXx+u}Hcn=_s-({PxzmMZ^xJ1SV zoZMY*FarYvO_@z8Lr2ep)%HgIL7rhYa~#X&&V8oYSw zA4m{3{hw1Vb~~26K^xro&e7i9eg^SqK0i}kG3z(!_~E?sjJlSWIWXJqKiHAWTG*SpPcCMD`kEc1gx`R^YkYWz zEN4vEIkj@&e4tC!(_~x`-K$w6CU%X7U2Y z)Y}T5stEyoSsB{H{+xfST3tov~6@lO}2gx#N(rHXiOAHT!dp6FiV8V)B4{L_P_% zmX0rPa^-{1xG6|#uEGo+!v)QAOjRe|jg2ICcXU!|Cr+LMbLHlhJ)ErR*P9*z$NLlt zmYjAUbljq004ZyOco?HJovV7M*Wb2nF8vT2D;3kGi%F)6Kr#TVW>}zTHnUQxoGmD0CY9J`|d%8@}n;_co2q zWr98`R_c@PQbMi}x3bWo4XZj{it6qYj+o*XvNoS4>rF;7WNn;vA*|A!3H}Wh-uk@n z*hV0S+XnX;K;BOoz?&*9_{NnM25s4^^QUt|>R!()^Z6#G3OmL{CU^-IG_M7_a~B+& zCrV;ouC1ljbK(K=ygqAE_-}ewnH2&&t0enS7}I4i0wJgNvCf|P$`|DHku`K`HfDa2=n@DCg8MRi_)vpMR2Mxy4PE2Qe! zD||kNXy=0WeU(43v%md9Hg9Zu#CP%d%C67gk_#pfXs8lf>M=betm(}0fdDKq0{26# z_c?J!Cgo-~*=wswLXkR|W8d+rDdV00`22Ouv=_Hod9bmB!=D$I4r@7DZX7e+0tO!9 zR{0d}A6^K#yRx@ykotO4(WUJsmFvN)d-o-wZ(wcDSUS`8jO-JSAMa4y@MK4fDP`(P zzxQ2})ofiauWKj9{Rm$Yw^?g=?`oO(Vf|T^I+-A+o1#F`>tn59d=FtgVJAV=y;G&` z0GMvtEeil5;e$Ln8-41(UeMl2kYLk%vPl?0+Egg_;g)494o5FsvdeZKP;&&fjw7o{ z|B+e%Z|)8Ts?=>@p|hr!nYXgV=ZjI4Cp#$E>+g^6r7Nd3<>-t=G%B5IyZUI{e{49G zqnIXEB=M@5Ndf1J#l5YWcLG=A4ufF8S{z5Kz-uM?Ni{{%mr);=l0=473h#cIc{K3> zZ-VUw_Ng5^HgWQhs5tQU@qv-YBej9`R$a^|lknX<*+sSVXue8M0#EPBJ6_Liwl*8l z_zoD#!l%WIXJZ$jm?|zUu0LdeP&8IW*(|39&QzKGnem$6--u{ZGtHt#Hro*h)?lu zXGKo-4Hv1WP*VLj;uA6UwGSV*6ro%PRbwR{@tXoCOb=OFTB4ru-|Id!rP5Y6LF*-D zy|t0qDSVPo$ffyoj#CIZV?l3VsPRYye$F^xxv~Z78_fwlCWbwW!nYCR2nx0_+@tg3C_UDMVa2Br=X3hfP}^Cp4Yg=#OK}K zKYVY`V9jEKD!UrCbSX6Xym2T-cg}!n;?;o{mM|zWj0P@D|FO-rQ zKt#ApEh#AX%_f%9!G6`I*K=bSnMIhQ%W5&BOMntzVr*eS;WR;FgM)+k`#+Vze*z&V zkU^I-R|!Nwy<~>eeQ~hJqa2|DdpX15kD=6U73Du;T|VarycBP^n#IZeIJ&H3S9#@oec~poZELqX$DAc>XZyuIqd^GK0Jq~0kI=d zA7gMo8%zmkEdnqMh)tkp?V0I;Tm3`>aU3^~dXw zlhdd3=iygnUgYu#GRhxln}4D?Gokczq?T;RjCk0=fUHy18$lt!-q!%sNxee7No^+N$9d?Es*``)0UJ4SC&FNY0pf z_MlbGdUy$|F}YDvJ9GTCkZbsNKj3DL5;=BGBx8xI;n)=A0d0j6MP7Mi6MQdk@Tux2Qy`oI_&*%EQ0bE?|R>P$rDhcFa8O?JIK zPOpFDa?-L*+Q7RrCg#y5z$l0d>n@+OYo3g>-Z*x&`Jj5|=*UOYaJer6;FAbdtt0O? zrFGUE?!XeUG}G8wMgeTs%+r;3uUU;Nq5EuU{h-g&UOBKhdS`;J=m!~xn*ztv_p@dD zR)tR!P=~5kX)FRsx9)uyuu?0dh%Ht7`PTM@e#Cq!z2ts;O;L)tQ1ipDiWqbGz@o_p z^D=UKR#`S7HAt4vQtD(_SeWyj_av~#tJKlb9>-s5Ykuzx_E1ZNl4)~f=zG$*;-y=T z2ozmFva9az<{2&63fQ?(Q8{IPx@t1LuFcxP-LXVctWh3AwazVTt2)w^*Zn-#eB`bD zSHoAusjOBK5(>uQPGj=ijdOH3jqG?(<5#C{*JQ?Lt~@zow=Ii4Al$Vr!#+Cf-gx)A z`_h(>b@7?*6bYM8%628gGW^rwWoG$mK_eCk`}B&llStfwHf12*{5spmTeNH$4{gCY z@Yuwr*k@%m;T<60bw9z6^WpWi@Bu^qe-g;YAzI+VjgsuZaGA=^G*I{KLy@rIjSpWb zFQNsCp2T;S$VaJtZ<(waRu8y7^X;>YhsWp zM)mKgCeE@K;J4vQSV z&-(Gl5AJCp>K*2-`U|4i;u3p8xo6(isu-38>cY zml1Eo&FBBKJpour?}q&nggpFiGM%m+YX`ng8P+uRnJiMyWcv*_AZ8KAB$w;rfmN8C z<-2EB6TqZO>A~P{*<);wYqZgxQS8E*syOXvGkGxF@s(scud0uv?T)fQ z(DGrwM7lvpitUG~6!*}kZUpBn9PuP`5^nMK@($xI^0Q~axP5qU>L~uF{R_<9&m z({}$$WuD1y-QzMVb3jLPk`~bDJNkw(Dv-6cKUb4uzD= z-w?i0NZ2K}AbT}Zi^uOZ32xmSxJw+6(3j%a!~Tdy-@RxVx6YUw2|V6JX+mSJNclfl zF~SD#eo+lnB=ZpHLl{)E+`sI^-V1Vn!6#Ml_W4aH*Pe(++sNI`M=5L3?X1z0;CJeE zJiX5Mp6JH*=R9W0t(1@>>1y=lP^F=yJil6JxU~I}EpTsBx?rJ5LbCbQ zuLBmmX1MO&!E}khx=+#hCesIB53`IWwqyFtR{AUv7vJ{Q^dn1S0@*^UOmRwctFy&> zd={(J@avBzmu$MbyamRMt_$kfHY<*v)%%&nY4hUDH=$k)$8LHlUG0G3Kv#T~-vQjw z)hXbsNIg?~b-jRw)ir5Q(gfwM+Zk+0haf z+4ER%>T8RnKAoJ-(s&tu&-iZ@A?^J|d z6md=9C4am*v2r=aa&a?~37bc($n#wQ<8UGXL+!RtrRXGSj-2INJ#+3J=}e6nOC}G8 zN~lvCS@rxoq7w$CLg-wx!%V%ymw>~xhUw4cADX*$A}D~{21F$!Y61aHwpdL!QcrsN zl~$s5kk%7HWHkZ43%mOcwlk3RcbKGQ*}K(Fxput)rpE0zH0vY(EyY=blQZ`odG#hD z)~{&r6XkSE(^csqsaMm>2c%xsT2&g_Nab1bTY%fIoNHatDY@C@Ei~v@19|F?szU6SWRS)uDXqNY!48RlAb;S*ijqus; zp;bteR835>3BXML2CewOM<^q3M*ubU`}gnI-oS&(vf=GF|JJB-inGOH_dc1xb|iqR zWgrcNy?1*8)vAlAaiBE%K3Q>5Ygy-#Wf$>FqL|Kvgb&6H?iQC*Z|PN)xZJhH#d#=a z@s9O0oea6Lg}submzNZ{iZ*_okZ$6G*h5YO!dE=7c4=YA9g$y%1xjkVl#|1DShEjM zH3(sS?uRfB3mhW5Wrm} zrY>KpBxM&CC;s5Ie_{o}upN{vdb8x<_$5iiQN49`z`+Zz`&E`yLAim;X&}$HAfKmT zkO2Dgdno95mWMH~h2c4);H=MigT8hyzl|4g;dU7F;p^X>w!fa0zf{^rf?>~ z0w{=F_R}ru{g5i@&xwC%R-!-1x|(k6pSb5_)$f`zyErIvSCs{z`iVvU4x_znFKti!!av6BkRX_=+kEc;*`_rla zB`g4ruCJGT3XVTTrlh3Yj>1>PNIy?sV%Yo*=qaBIOY87_?P04yx6TV?_{~K? zOHEo3|2EA2JAMPYZM!H<{|!s-$r>l5{19icxV`Wf-{<0I>{v&H4FZaCy$B6Ludz{v zRH!!HV#JGP?5(L!Zp#}NlOODgWqjO+yo~+LasPYxH+ht2KjdfCFQr(oovP3?vkFK^5FvPJ4^LD=DpYQi4tUXuY1;erJaBQ79 zHcp(>mKvoD+)bq5SX9siR>(%CL??*D>Snn%p}NfGO4(RY^puLI+j$Pw)NZLb5bKo{s|0L~ z-A3R~;QHMg0bHSgESOM&N&@oF4|8gkPF-nVM=sQ;d}wcS{{!iW-)yQ``D6t#xlh(O zRF0Z@O>0uMz9g)u{P))ptV5lH2(gC8I5i(FDRG5Gp1bgBydKgxJy5gBfK(#D7NzZU zatG}S^z#KL*Do5=K*F7hk(`mbdgI1XoM!8*-};#UzNtEG@Nki#`7)GfV;VlfW^)=` zBaAjK5>gx@wf_D!B!2C6xBK^K4%x|+#?P@5N7tlfWo6xWJD~Wz^cnPfFF($Ixt4!j z9%x^1$on56XZB0Irm^kw-*rd1YVO;(*LbB21@7OPJspo%WO676#~oUMws(zP#+shG+$ns0IC3W z_{kYU>N5<_6=j>*0d}r-?8U+--eXfy2M+opoYL|=I932TMp=&k#tzJ^72OtRJ8BVOvTYPh;@EE=LJLeOk`y?d|Dd9%fWlhON^LnB^6x0LyZqz@imyogJ`$C@Lr9Z4o)ZQz>NCavG$$@e2#r3 z4I=}I5KgV>wl)~_Ja7gLQGju0c1{h%cV&6c`doWWv$>q*=ZLc8J{hBiKXNK?zx2Nr zz!pph;BLU2OaZTv>Pzj(VpSp2&OWNCF<~>NgL!nezhxEgj;&2 zl>z@V#>sykFCnFL?|(j)J3SFr|FFa`n@KbhC2pZB7 z#3>qIn&~mG_Vki=p8_x&CFeD4V7MvgJlk^G7H;(apFxr+7Gc0+1KfI6$@aeF+d7DJ~_-A|H=0?Da#&^Cqb=!=fVz>giW5nw=jWQBS%L^t1EZ@ zCm9;qlG{($@0W3T&l17ownc5pWhfM8Mwn-fLtb7H|IYl)8@QikEc_Le+s60x?&B*m z5kObB5{BD}gGr7l84~vP{N)C~3V;xhBWd%=^j0&KBw3T3-HU`;hqWA3OWW~<8nl-M zfYn-BI0_?g`3$_;&Exw<(G{QM|8)Kq28x9NF-F$>r@_BO)t^T*i-U1bX01<)zC_uE zR@8qEQQ#cm$YbXIUPVO?z7KI$pw@r=-V{V@>dC9Hn==1QBVy_b;#*jR+&f*$AwCl?o&G?2Uk4=*Ej zFK^Yvw*HTO9n!XRBWe++o3)4O!OC9PC=_l_<$M(W8(Akk`zv5?nJifb^rH3N?Hhio zo$=nNmSEz_QFHj|XF!vQEcdqPyZz_4|M_GBH)k)KA9XGRlTJD;3*y1c#?ZWkeaQM* z^`Bf04#Z)ARgrE4rMmlk8E5F=NpaW8xKNd3)-orW$m+kh(W12jQbQ7oi z)=#qbmhkplt}u`FC0sV9sdnb5$E!zX_xlA{4wW&j0*DCm`=1;Sh_sB1xiH@C89Z93;8d)EUk=lPNIZ`o3H`Vd+Ig`=CV}#?PAXvzWk{x96fn z0(rYh<>?PJ>Hd8v@c8=*vm+)>P1k@i2>yMaKw2nihLV6Z;wcdc*E2{8=xNh(FkEe3 zq_pc;ISw&}`?lqKx<4vIa67!xu|P}G$c3MDyg?u^InS?uM6Zzys0QM9ChW>g-ypzA zkOUSfvhTTWq{_>TJ{+kpgwX{@>P5ptiJ1NTO5)8 z8BiLUY_!*AJ$V386^TicK@z0qOPWP#Ea5?}!$_&fQ zOcRKuR^tLX*&CM(ahYftiNg!a=uU|He)2nU2(~iX@Yo|foZp906;o=d%aK09YEW7_ z-yX*;XE#z@?zZ&fQ?2fYX!T8@-$(K5Jo+AkyOM+(944x4B%2NR&avFFJY^9_br5UtzSX5@gmYYm@ z@S$jtqFn18bXQr0IYhQ=+2~ZDB_DRW3d=*B+3q`-*1P$i!GVIG(AMp=vBQ#^_mNxp z(;4Iz#_~&9jZ}}7oW?R;_x8&h?b0N326NJq4~>W^TeI^!o4=G5G{|9ff|`NN5+?ns zL@IWva(*@PXPmVGQ#rgIOY*nnoqNDDy$hd2uMT>wBgzg>YT&BV2U{k1ah1(1j_v0` z@o;6~SUGW=!+j!oa9ko_2^G75?VolPmWk=Pb-h{k=phZga( z88Rp7QzbHkpYG!aug9e^DF63Bi|1#CeAW^CpakO9DTT!p$yhuT8Aq10^cl2O@Zl-2RXr`+zCPj#_FqXs}W2{Qvn2Y{BmNsG45? zB{BF_rVgT$u0 zE8o6|@C>uOK1Ba}!V zx!M$9J1B7#_JSs90cKlucib?T&HqQpLE9YV1?v{gh2NWKEt9FX8;3DePnCL5Z=k)Flp=?-i$<5H4zc z`?2ZZ+p~Y8FYr;m3Vn2(u5Z`Av6#S}zkpQpZ|vNP0DY^I-oa$HXzg+ajQC7%wldRN zfOAL!UwFtuphqqR41v|3He4cQF5;UU9M~lti-k<HSTs^#>-Tf|C2&~#m%6WZAy1jz!Q_-IbpZP z8ht8}UG13lz+N-7+01+RlE)6OT^3px7fn@1|_b7^{bhPet}< z_)77(<^>8-qQ2X(n4faVhm@T0@Z{5HFSWs~EDXtV@7IAMbVUP6;v8^%l3PZ#wOZ-* z*Vk4lRj6OYpAZ_$*`t|tYKmLar&&{5{d+5cst)rQTn`n8>Xi+0zXc6YbTPMgzewFg z23F=+`8=FXXF6b*CDVN$v3|6iy;TSFSYh$qrbhKDcT^U9l zj}3g#zty{k*>s8S+>t|cng#3@Rz`z}njy{*?90mV6_Mkvv=iL9pb0ttHf$7;TxkX1 z-klTGb`2~-Mxx6~+{b-KiFd3XG`p?+6-0PMorB#Q@TY_CH5)En#5WrmHqj;@Fvi1A zeGpO@wuYIPOgRY&02e-U+j7!$LZ#5mS72R3MJS^gfheL5`kQV_n{8}KXaj)V%4b~As zFrQ7yZal}~{ELX@8c#V?2LlM@)g(|;VvcBjEuTJ=`WkOem{DL!+7Lr!U;F!mGm_^~ z+V^T?%bz+8noq9{ybcq16Gzd^fS2`skac)@6|;8X8l6Q19epZ@l^3@1ES!x2XLNA4 z_FI8#x5sq7hXVr83D;_5$sU!*Ye}zyx1wMC?Q{DSgrUx#fM?_Fj@{syA2x2yL^J{S zPPLkQ#O+9E9a^H*USdriL6rGHDt$B!vu~t7^)@_e=(<|SVd!MenX48AP(Z$4WoC9_ zeN;I;hEAr{ZvB^gK*1AWfI~5H0a{Y#2UBjn9`7;3JDrI5leeufemoZol*pDlVTSHP z3#8@6kxsJwUFg9(;)>Xm!{nsFC<7}Xwv_?o=eP)$>vvvj>yw z=YS7{pIOg(u@mJ%G0G^TM@L6>l)?_{_e`(yLxmX%h*D zMJS13@e!}HFR{?GNtq;%=4#zUgfFP^$g|Ax1<`vC&qIPbwGNo}3>ZM?=Evk6r|J&S zi$UD-za)A$kcqu)8)1mG z{FI*zS4{wM6S3;RP-!$0&8!6*;>|%T%HJxZt}cmap#~4vD0Pkx22gBbPo~=2iEMFa zSN<~qRz>jf54?e)>3%j;Gc6C1_YO0C|CDQDt7+bE({$0($tizZ)xn2L?@6_ zR3$`yiwH?E%X*^k*^oQ=z!1GA|E&fXHPR=rIEGq4%0=SGvror2Y%k#d`aPmx5@~7a zdkmPa1d-<`6M%& zp9rn|?C(5SRowEcasXoE$)s`=GvJk9wPt|2VX31T2F}6x3#(&IMqZND*a1muBh9?X zX_HSLo?$y$a;qFx^U1W|YAd%)Gaf|AEHqZ*{PW96FF*&nO-@c?c6t5=K_z@2f$8<^ zY}d|9NRviy7sF$61>@bV$B3*VeDg4DX3qScxVTL~5Go^T?}aG+th- z2`EduJx~ZcSssR;yX%oW&ze|$TF?;>HGHp~Eq?$w&SAD?d#s$$|4F@l*T7}X$7>}7 zRvPwxrPaLO5X-qYiQ7{P^4Ui2GDbq&DJ3Yu`)8zfMi1{>HEq`+uR1bJ4x!#n0D6_M8Zs_# z3mc%u30aK|avL-!XI&?{^%v4OXUr4OzaL*|-HV&M5GPx)SUqYMWw@Ex;%DHx^&FOD zncjYHD@AiYbGx1O(rsKW>Eg}cid)6bqA}!r!G{?x#)c?^k+q_uv%Xh3ha^A^{%wnpRPY({1LqK{NQy>!UjUc8f7x2` zgyLiGpsKlFO75ee2#drn3Glyna)PvUP}e(t6P z(8^W6g23+fzT5gZQQ^L-Yg#^P;QK8FTZAe)*|CKS6(I>8a2aoN+XEkYf2jAF!Zi3! zjS($tF@bu(ypeC>`IZtF;jz`F6A-Y7ZUQBuZxp&q4zHb9cc*!1`T3p9xL9`nWhNVr z!2lf=fCA>;1E&E|yfmrHqB#XnUCu28b*4#eZ{lLL(42#`ui?BO&uZj|d_Fh!Bw8g$ zn@2uezsJz@^XM(T{!CEw+EyG*eaF`FuTN%C zOZg)khBpDobCl(3ud$bhr>EdmuQ^l^Cic|y2m>LM+gsZGYKUAeJE5YUX9}j^JDoojv<}Cm&t+agmp?JE0%d#fo}m_cYogpjn5&egilTvDFz-Df}1i zB4)bXfn$dqb!cCa13DdCgMNehaa&${n5Mw&bxeKfNmHq%e{T_H@WB!H3QgFK2gNpB zP<;xkez-y-Lr(0^P^G!YH~WLut`0=mPXbVN64iv6Nd`s=eUQ;?V((+QU0&B4SF3*{Pm$AVrq;v&)c>VLy_UCe45VEsI@ZWM2TaB# zRU6XaLx0^H=0)Z!$rIu`3*s{Z!W7pU@6aHvX*vUuzME+!B5H}k_gFD)3=f;nI zi1|B!@iO%p;L{!JSEI~vyUByf_{HY=;RuAK##-h!06XFwxYi?xl}oWStJ*P{OcVe~ z_v(y8!+BaLQB`(D(XrL0ReKMn$R)8mU2@$q$Pq; zbZq-$IkP4V(`m}e<)cwnZLrjiA-X0@VY~Gi5-PKX20#Eag!JOw1br%7Rr}`(v@d!u zCo@&wE1SwM=zt~$K!eJ**9GAv!}Cogn9(d0X~BwPkU4gaWh?WVRcE3N?C%_R_D)Vw z(YmJTJ_0~fhItqHPqoIFGQYE2!~?aSRa{vjcDWhy5>oT zGOMFTWfL`aLx-!QL(9r?~D6y9Uhq=af8z!rqg#p zXk%gE-;=@G>MUv7p@P#ni@zP*$YQwA0Dlc21`%pV;p!_F@xI(^eA5&SZ{rU?^Wj}! z6Y%C^eMYilc_~MAwqV`h=I0;WA)MqJ^$IvyJ-O0)*RuLYjTL1TWd|(NbhIZ;nOop( z`4bc=fsxaeI@zc!vvYFFetFRKSMjef2_#oIzzPIxZ4oB0sxKOzX4Wltz#G@LD2Qr5 zm9o~xF;EU*_!O`}IigC{sU%1^$$B@>Fa_H0*>*1Amc^7tnKxcPpr8zZTme`6(0@J| zXfBE;0)lcuv%tqq05V8P2B^)Nhq~qdR|1KCfe>(GeuFaNc)T~zvma>o)FZv;sVD@D zynx%jpd8m<{zI zz44BQcmN85TNhy2plu`Nt$b;sKELSBpW)my@*ZnL{lFaD|7-8c-;zw*wh@(1yH+~o zQd6mwOU~P(B4CS|mX=v+F44&NRvMbQpcpDmU!|BhndzGgrsa}~;RGs*v>~aLX|A9$ zxrCyC3y6ZiciVh3@BH@t1LJY%FM8{e94DY4JQ} zYS0fcOC|N!{@iq*a@H$Qe9ONriBWJrhLhC?o5K2)!=~i)0hGh-mMd~RkqdIGCB(fU zy5*IvHssJ&gxudt>g(3w2{)axskJ_#h96qTc~<{c!`n^f zg+SOfdm8=UI!4%}d%RkXd}yWU1H66h)eDTsQr!qkcZE^zbI#F$k(dn7l7z}@YSv1+ zIcEYw{HJjfg()x7R@zQ&o;LdJ2vi6Fkl?OHM-Ga!%w}co(6=I5LZ>n{9pr~6!z|S$ zq_VfE7##n|{H(t$wPI-D`~L#((@V(MZ>p6Eb8k%4{lIGT;hZ9cg%~HhcbDCd%0RbM zs?uZG1wSL{Z0f+NzDiO?w9~XT^dWptKJ@M~0(@5*az*ZgabU465JN9eFY7vD8Wdz_ zlAIonnlivB;uDXov3sIgoKx2>G6a;@?v0qg;r`RnZ{4wMw2%}(e*c8k`R7sNT@>H} zfUU~mHR~8!4rJTHVlT=v3wz2kx&95Nz?@Tj8)s5E}t{|AFA=d_Y zOTqb{ATx>U``k~NJ2hYk3r#Gn1}|1Xj}jq!9%;{k(?9!WZt1z#{OATvapC-}#$LWi zi2R>~v0v6A<|?Eg)Ye#VyRyr7RJ$N4vFEFfmb1jHF(yZN^rc!ULDen>KWu(D9Z5!P ze(qg(G2HmSqyi2B&W`vo@N=3l?+dXbWn-`1LrY1^_mSilpKLLxQp}@s?=Tqw6Do5Pui*IhPZtaT|GAE&MF$;(4s9Bt5f+vbITElRv3( ze&@3GgY%ltiz;PZXq||TeA+sP9bc(#*G<2ck&zF3W?0$Bxit`EwvZb7jke;810>h3 zb}}!oS_xUbJ^$_PWrSlJ-;v4qq!@|L9uM#ALcMu|+|fni+AqPpu+CtjBrs#Y1jKVU zEc6L$d!2l-MgMi5&7?{Dfxj)qn;mIZudn7I6V$88%05A!PtCQTGSxXKMGh;qXa|fE zJBUmhM!}@e#A?s%bajm+=Ka1WxHZWaj;k#XT{T#;bH9c5zA8txVHEz(EeE*PP9eD9 z<2|evdxmVLj_n@`lp>6@ zy_ZTczm54_lGjPwPaq$dF1HdIks&Mp;%bge$QZnnp${}#&Z3)z95ei@b9;c=kJpY- z$G#RZbgyTi3&d4=3%+gXOSp|g^~^%K1id>re4gTka;7m@WA}bFo`GUbT8-n19VVdO}IkuW(H_iil_S}@$xy(Q*fCcNaD60 zxqsWK5lESLWnKgy^ci@da#k9^aW5)oLzbFxlUVBA&UM~79PF7=rW@Ot`>9(Gju3N{A4%EK0dPuz{=J_LUv|Pe^*x3eq_ExMNjB3?{$+xH^_Y z;e5pH)*~Lo@y=;b=P$Iqp9KR|j(>D-kaI4WeI&&HPFRtbZBMiQ^PwE`pF$Z7#(@UF zP2~&InXDTNx3`4)H2mD8yHl{Jk(|C(VA2vwY}3IRqo*qy9HvN7a!$$hlZqjmb6tZy zp1fLd^be5LmcI`_d3@@A`jLDS!b0qXVvP%y>+DfL86Ie=*TZ)PL??Lk^F};4=dwv; zPRBV>*)f&NE0vtjYHw@vs9l(Dk*g-}ARSciwv!f)E361d_9y<;9b7)PBw$3dh`AZi zAY4)BVh3t>;gR=s)nZW3PT_3bOLDK)eTZT^*m%P!HdC!FvK=Z=_iA>Bg!`SsC|P3u zz+oMr^PUcTebccFK>bqp475+?5RUC{Y7klp^p=Q;ZM+c8Zq6wBtH*5c=QHlp7wZS%6AszeebN>>_2^H7uuK@g%1{vF}DT>U{h`}c+u5ubXcFMH)fZ6-l z!y=qVN>jqgj)3T!mALcM;1!8}PDcMCU6<9?l#euNff${zE=b0d%;TcPFfw`y>zjLg#_WgnwatH|t}Y&WrR32m5W_AWNa`OqIc{ zW{_mX(Ck1psRCgMhJ*hXhcAG1ocb_kuY)%9rlYzq8h$K;X}=5m+8CYpJ4Yw6zLi%S zpu}dkAc_hVv>NfWy9eLsQ-6OzoBl{WAkRi|U;anmJ5dFwz(C9~-A(!Vfw z(E!S5ua;@}(q5GrIc6|PAOSPg{il$s$UBI}tk5xuP-VedGyZd}xqXvWvU_`{;Cf0> z5fN79T(#iq-q$RLb(of0ZA0lfepj^!a2-6 zv{v^7r2J*xmj&XVgZ>Wd=RqwGGe1`-Svll~bz(-y7*N1ooU5J*aY@&5ea5ss6n(a? z`N9l?w~=^1g2wLDVRD5ovqLc^Z#YRDFR+QYV4emH*fzOpzer3>Pudh??f``be>dD3 z)xB}1O6bZpnt=j(m92Fxq0dz89n>B05xx10QDL-YDz&e>h_u@9+RG)Pv4{2IYNiMy z8auH}j+fW*;q%Ymtbq+KI_r4gxGUeYJ>hq~vbe!N3%NntH+Dyh7I70!cu(qE_`Vp; z07NvH4Q2s#9;mKj;>umoviK|H+#CbgGq`D+QxI*$r6&D`yf%-M^{H;6gi4*j3?c9c z8$}NK?0I4%b?c`p2;SvL3*xY`0fe_KIZqPm`M%{DCrPUt{bS|zlhbHBNlUe7zcK}E z$L2zIl+z#Z!thJW!}{G&JAC@Pg`H(}GLM_m;uV}C9Yt(vF+F0Dy7{`k zY&v=ZZf?8^qSD>~2iP#{qQK632aMplZye6Q3X>dctS@JHSz2)zJaqXvFEZlr>9$oY z^&9^4pN`1EJcEw_wi@P{zJqQX470?WZTB*5Y7F!3#xJO^z|Gw@)bFoY5#daTP5OgI zcbKI$Ok(|9g_%#If*$3ga=U0_n%|#}eWwyeW~(19Te+!xF*(rd=LU(nM15;<7Z&oA zrqIw#r7}&_qgCdvS7+!|3?8w7JNRtHQ$~8Yyw(xC+n=- z7SQBo3+)tbg2NJn^=lukNOCkiEsgt~4tCrZ{aSnrHRMk@_?1^whFrEn3mT1NSC9B&c-(JrWu@FUhSNf+(>-_%kX#@LYnzq`^M#XX}(*!_LZCY za24(5Y$WH^=;GY^#0c{Y4{_!GPvm_bd#&6ypUpfwu%|+=UEe^Q+oe$7cXnyF@O67L3%SKO#rdayD^4^vH2hG{w%vp|_*jKf4 z=jb?40UP4S+Mi~(Uz(^cvgVB+r+Rt|;wnFRYcz(i=&Q14Ok=V-tTPw4%v&;ZrxI#w z6&rvLjj#yzBr5~N*7o09CkIE=>EWwo`ceL*@Y=504RB*xY#SY{)p3Gvn9zBL_FCN0 zl^axu8p~su8HpiDNi{%5ojAv1{0?t7*mflF9&Y_x4#)X(jyLl~c+s6*I1G7{zBI;tH*_ z94)o##4$cU4ohj~e#C^E><)3E`d;ftdwTQZpDmp)9)n5^+h%BE?)8LI2A`L!zjTBL zPYE&+#0&jDFc&4Tg}VC}E@4ZGyWbiK2dvn6Mpu!cQT_^6!RG!7)fE>V>?PNFm?vc5 z>A8gcW=5Xm2#LEW_;XgMQ$=Y-#lc|zs2}}2ny_4Kb%D@Vrtu6rOmUe!ph7;;L`XHi zXcDHc;OYbIk44?|A9-=Ml{Xap)^{jb5$Kl?v`CIT`bDXV*x{h+UARtzOd}#US>a%X zOdU`5^_P@lkQxB*B<&RQB?FgJOH2-~rMnXf_{5%~s&OlUM^i30FeOM{`XOXs)3_BU zEAyNr%bz8RJ=Cvw8y=)3p z`K|i!j$l~LqQ)kabHK}7WeyB$x*({t#cQWf98qh&X{R*Y--9)~g)?XCL>&z;v9#hY zTFY?DV&1fPE&*z}6Ki`Y5#(-eVYB;OzZjPSDnN%ArA8D>wODpQT4Jt}ah556JE+G_! z_P0uQ!qDhR94VdpAqajIOl4~>oTaQ8H5yXaTZUOb%cRAkWYV?KSNlTqgSM=Wgf)JP zz=?Q5f5zPEVO!NbOCbqEwP^Ff_O_`gdm67#U{Mp^_bKcq2IoO%zcJb(M5z`cjv1Ck z+!awNRhwjj6CQqu+xC#{UWo^3+h?6ymzq3r?3JV}<|u_9x=MWAm`1AqAnOsJ*@)^4 zr|`FkZlg{Cd!#Chmhn=_ZQe;~-DTUOv>)Tbmh0{z_42vWa|vNUO% z_5KA1xNHBgw0zjUH|s5xg$b4k z@Koa#-AFizrr6h2#$k*41tm7_jp$yL4X*DZcklq!u+>9E0WnhcOFPn7Vh^ao@~tno z@RwY)*+8&|Hpdq)`a=L*Teuw;_B@u;o!a!YaOO@bs-?*gqpm?nRkXl~mKFfF z+OVzE%RlC`M5-+KM_GXZ@9b;=2C(sq+R&Ko_RzZ%5P~kDieK3yzV4BN*{$E%KY;4k z)s?*vacHYN~u+?SoI`e@S2!9Co!cdvz;@N@{yj`0-9^8osR(V7PR-O&gM)x3owqs5oJpIwc zgY`#VzjI$V>YYDrIr8D;0JK<10@ycefw z;;oV(!gUR*xBg%xTl-#d>u(5}#jFrLKo}q0b{IuuZhuO7n++ zo@9)d#`(AT$mbW5g;c;&z>1_2Nk%;L?TIhfeK%PYp>5N<5wdihxw4-qvVsN6t@bol zDFgi~t`B&ZU3ek!#fXVE5Ao$7AwI+@amT_m2SclwQE{cLcv3kwhokq+!S%>Fe_*(Z z75)vhq@YqZqa~Hf$0S?T@nr_%mV%*aT${~4)6|(P@Bq_Q!VC4tZa`7?ra`4?oV+wSr2`TVSUmKS_>V@3%0*S#!+L=3f@oF=4k9U9xv0p1;Fx&}V;X2J~h zcz^}G3|;s8JyEFR*LB*fPUm+?f+ofnBQ5uK%NrwA+RV_~h<6-mw_wU?NGRI!zNTh% z&>ty6x8&gW75gdW)?p->&%?{*brS|k@b|(>&<^nyO55Pi_q*eK)=J*Uunw2cw--p%E!VXuDa? ztZ$HPKJ6$Sh7!UrpxVBLFSnpZOw$(ftvg!Nk1LVfL+FL(u zh1Abu(oCSmgqQ2IrE;Zz2f2DAD%T4XO6tU&)2IB}vV3{^xpz1MYFEPy_09RP2QvmA zIqw<(UaCnCs!mFX$+3sjnV*(O5)y`jW!*wzF-l^K`Bxgap+0Ej z@c^nf{Ic`6I5#9bcE7fwiiP8JZ9dr3FsD~SBiW_`8{UgFt*{$@qj#E)90JYra>Zs3 z$sCTuzOye2GdTO;4@;wgJK@!ij-|c--insluCR}{#q=D6Xz#nL6;`rkc*UzLTR%Y{ zN2YK;Zcz4YY=+|(0_?E=#~3U@I1fIyRiBF zIeWj=id+b|L;kSMs>NMfeB^(={IdrC;NYJy_$L+olL`OdOqgH0OpSa?FTRhwb<|%A Pe7HEdAEg|=c=LY&YVNkY diff --git a/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100644 index 13b35eba55c6dabc3aac36f33d859266c18fa0d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5680 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 diff --git a/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100644 index 0a3f5fa40fb3d1e0710331a48de5d256da3f275d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 520 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 diff --git a/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/wireguard_plugin/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100644 index 2f1632cfddf3d9dade342351e627a0a75609fb46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wireguard_plugin/example/macos/Runner/Configs/AppInfo.xcconfig b/wireguard_plugin/example/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index 20724646..00000000 --- a/wireguard_plugin/example/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = wireguard_plugin_example - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = net.defguard.wireguardPluginExample - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2025 net.defguard. All rights reserved. diff --git a/wireguard_plugin/example/macos/Runner/Configs/Debug.xcconfig b/wireguard_plugin/example/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd94..00000000 --- a/wireguard_plugin/example/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/wireguard_plugin/example/macos/Runner/Configs/Release.xcconfig b/wireguard_plugin/example/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f495..00000000 --- a/wireguard_plugin/example/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/wireguard_plugin/example/macos/Runner/Configs/Warnings.xcconfig b/wireguard_plugin/example/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf47..00000000 --- a/wireguard_plugin/example/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/wireguard_plugin/example/macos/Runner/DebugProfile.entitlements b/wireguard_plugin/example/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index dddb8a30..00000000 --- a/wireguard_plugin/example/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/wireguard_plugin/example/macos/Runner/Info.plist b/wireguard_plugin/example/macos/Runner/Info.plist deleted file mode 100644 index 4789daa6..00000000 --- a/wireguard_plugin/example/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/wireguard_plugin/example/macos/Runner/MainFlutterWindow.swift b/wireguard_plugin/example/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index 3cc05eb2..00000000 --- a/wireguard_plugin/example/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/wireguard_plugin/example/macos/Runner/Release.entitlements b/wireguard_plugin/example/macos/Runner/Release.entitlements deleted file mode 100644 index 852fa1a4..00000000 --- a/wireguard_plugin/example/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/wireguard_plugin/example/macos/RunnerTests/RunnerTests.swift b/wireguard_plugin/example/macos/RunnerTests/RunnerTests.swift deleted file mode 100644 index 7449c9a8..00000000 --- a/wireguard_plugin/example/macos/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Cocoa -import FlutterMacOS -import XCTest - - -@testable import wireguard_plugin - -// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. -// -// See https://developer.apple.com/documentation/xctest for more information about using XCTest. - -class RunnerTests: XCTestCase { - - func testGetPlatformVersion() { - let plugin = WireguardPlugin() - - let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) - - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertEqual(result as! String, - "macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - resultExpectation.fulfill() - } - waitForExpectations(timeout: 1) - } - -} diff --git a/wireguard_plugin/example/pubspec.lock b/wireguard_plugin/example/pubspec.lock deleted file mode 100644 index 55c77662..00000000 --- a/wireguard_plugin/example/pubspec.lock +++ /dev/null @@ -1,315 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - ansicolor: - dependency: transitive - description: - name: ansicolor - sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" - url: "https://pub.dev" - source: hosted - version: "2.0.3" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_driver: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - fuchsia_remote_debug_protocol: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - integration_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" - url: "https://pub.dev" - source: hosted - version: "10.0.9" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - process: - dependency: transitive - description: - name: process - sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" - url: "https://pub.dev" - source: hosted - version: "5.0.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - sync_http: - dependency: transitive - description: - name: sync_http - sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - talker: - dependency: "direct main" - description: - name: talker - sha256: cf02a0d294701c76022f32bc8eb7e6f943953eb17fa1f8aaee56af210848134b - url: "https://pub.dev" - source: hosted - version: "4.9.1" - talker_logger: - dependency: transitive - description: - name: talker_logger - sha256: f1755d517e5ca8b119b65ad2fc1079746a8d03bd565e75d6b9d5aedf5c1d5b15 - url: "https://pub.dev" - source: hosted - version: "4.9.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - webdriver: - dependency: transitive - description: - name: webdriver - sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - wireguard_plugin: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.1" -sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" diff --git a/wireguard_plugin/example/pubspec.yaml b/wireguard_plugin/example/pubspec.yaml deleted file mode 100644 index dfd2e107..00000000 --- a/wireguard_plugin/example/pubspec.yaml +++ /dev/null @@ -1,86 +0,0 @@ -name: wireguard_plugin_example -description: "Demonstrates how to use the wireguard_plugin plugin." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -environment: - sdk: ^3.8.1 - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - wireguard_plugin: - # When depending on this package from a real application you should use: - # wireguard_plugin: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 - talker: ^4.9.1 - -dev_dependencies: - integration_test: - sdk: flutter - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^5.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package diff --git a/wireguard_plugin/example/test/widget_test.dart b/wireguard_plugin/example/test/widget_test.dart deleted file mode 100644 index 4183e3e1..00000000 --- a/wireguard_plugin/example/test/widget_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:wireguard_plugin_example/main.dart'; - -void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); - }); -} diff --git a/wireguard_plugin/pubspec.yaml b/wireguard_plugin/pubspec.yaml index 0a06c71c..65206fe7 100644 --- a/wireguard_plugin/pubspec.yaml +++ b/wireguard_plugin/pubspec.yaml @@ -3,7 +3,7 @@ description: "Flutter plugin" version: 0.0.1 environment: - sdk: ^3.8.1 + sdk: ^3.12.2 flutter: '>=3.4.0' dependencies: @@ -15,7 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 3e69b72bcc45b3d93df451d4e6a122b55672e049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Fri, 7 Aug 2026 13:44:14 +0200 Subject: [PATCH 47/65] fix primary and secondary next button styling --- .fvmrc | 3 -- client/android/build.gradle.kts | 3 ++ client/lib/open/widgets/next/next_button.dart | 17 ++++------- .../widgets/next/next_circular_progress.dart | 30 +++++++++++++++++++ 4 files changed, 38 insertions(+), 15 deletions(-) delete mode 100644 .fvmrc create mode 100644 client/lib/open/widgets/next/next_circular_progress.dart diff --git a/.fvmrc b/.fvmrc deleted file mode 100644 index ac62dd32..00000000 --- a/.fvmrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "flutter": "3.38.10" -} \ No newline at end of file diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts index 89176ef4..e53e87ec 100644 --- a/client/android/build.gradle.kts +++ b/client/android/build.gradle.kts @@ -3,6 +3,9 @@ allprojects { google() mavenCentral() } + configurations.all { + exclude(group = "org.chromium.net", module = "cronet-shared") + } } val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() diff --git a/client/lib/open/widgets/next/next_button.dart b/client/lib/open/widgets/next/next_button.dart index e5deec9c..5df9dde8 100644 --- a/client/lib/open/widgets/next/next_button.dart +++ b/client/lib/open/widgets/next/next_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:mobile/open/widgets/next/next_circular_progress.dart'; import 'package:mobile/theme/next/color.dart'; import 'package:mobile/theme/next/spacing.dart'; import 'package:mobile/theme/next/text.dart'; @@ -86,10 +87,11 @@ class NextButton extends StatelessWidget { // Style variants (minimal styling for now as requested) switch (style) { case NextButtonStyle.primary: - backgroundColorInner = NextColor.bgWhite10; + backgroundColorInner = NextColor.bgWhite100; + textStyleInner = textStyleInner.copyWith(color: NextColor.fgAction); break; case NextButtonStyle.secondary: - backgroundColorInner = Colors.grey.shade200; + backgroundColorInner = NextColor.bgWhite10; break; case NextButtonStyle.critical: backgroundColorInner = Colors.red; @@ -186,16 +188,7 @@ class NextButton extends StatelessWidget { final List children = []; if (loading) { - children.add( - SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(textStyle.color), - ), - ), - ); + children.add(NextCircularProgress(color: textStyle.color, size: 16)); return children; } else if (icon != null) { children.add(icon!); diff --git a/client/lib/open/widgets/next/next_circular_progress.dart b/client/lib/open/widgets/next/next_circular_progress.dart new file mode 100644 index 00000000..cb9c8eb6 --- /dev/null +++ b/client/lib/open/widgets/next/next_circular_progress.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:mobile/theme/next/color.dart'; + +class NextCircularProgress extends StatelessWidget { + final Color? color; + final Color? backgroundColor; + final double size; + final double strokeWidth; + + const NextCircularProgress({ + super.key, + this.color, + this.backgroundColor, + this.size = 20, + this.strokeWidth = 2, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: size, + width: size, + child: CircularProgressIndicator( + strokeWidth: strokeWidth, + valueColor: AlwaysStoppedAnimation(color ?? NextColor.fgWhite100), + backgroundColor: backgroundColor ?? NextColor.fgWhite30, + ), + ); + } +} From 1c09129de27660f3b5c674b1ecb4d0b26346bb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Fri, 7 Aug 2026 13:46:26 +0200 Subject: [PATCH 48/65] bump build workflow --- .github/workflows/build.yaml | 4 ++-- .github/workflows/lint-and-test.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d2ce5038..73b11aac 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -176,11 +176,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.10 + flutter-version: 3.44 - name: Install Android SDK components run: | - $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;29.0.3' + $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;36.0.0' - name: Accept licenses run: yes | flutter doctor --android-licenses diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 48a2de8f..29575322 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -47,7 +47,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.10 + flutter-version: 3.44 - name: get deps run: flutter pub get From 75d40ec3d913ba4be02e3a21eb1e1683d830c35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Fri, 7 Aug 2026 13:49:10 +0200 Subject: [PATCH 49/65] remove testing screen for next button --- .../open/screens/testing/buttons_screen.dart | 122 ------------------ .../open/widgets/navigation/dg_drawer.dart | 4 - client/lib/router/routes.dart | 13 -- client/lib/router/routes.g.dart | 28 ---- 4 files changed, 167 deletions(-) delete mode 100644 client/lib/open/screens/testing/buttons_screen.dart diff --git a/client/lib/open/screens/testing/buttons_screen.dart b/client/lib/open/screens/testing/buttons_screen.dart deleted file mode 100644 index 07364605..00000000 --- a/client/lib/open/screens/testing/buttons_screen.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile/open/widgets/next/next_button.dart'; -import 'package:mobile/theme/next/spacing.dart'; - -class ButtonsTestingScreen extends StatelessWidget { - const ButtonsTestingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment(-0.7, -1.0), - end: Alignment(0.7, 1.0), - colors: [ - Color(0xff5B83FF), - Color(0xff0036DB), - ], - ), - ), - child: SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.all(NextSpacing.xl), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Text( - 'NextButton Variants', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: NextSpacing.xl), - ..._buildAllVariants(), - ], - ), - ), - ), - ), - ); - } - - List _buildAllVariants() { - final List sections = []; - - for (final style in NextButtonStyle.values) { - sections.add( - Padding( - padding: const EdgeInsets.only(top: NextSpacing.xl, bottom: NextSpacing.sm), - child: Text( - 'Style: ${style.name.toUpperCase()}', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ), - ); - - for (final size in NextButtonSize.values) { - sections.add( - Padding( - padding: const EdgeInsets.only(top: NextSpacing.md, bottom: NextSpacing.sm), - child: Text( - 'Size: ${size.name}', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.7), - fontSize: 14, - ), - ), - ), - ); - - sections.add( - Column( - children: [ - NextButton( - text: 'Normal ${style.name} ${size.name}', - style: style, - size: size, - onTap: () {}, - ), - const SizedBox(height: NextSpacing.sm), - NextButton( - text: 'Loading ${style.name} ${size.name}', - style: style, - size: size, - loading: true, - onTap: () {}, - ), - const SizedBox(height: NextSpacing.sm), - NextButton( - text: 'Disabled ${style.name} ${size.name}', - style: style, - size: size, - disabled: true, - onTap: () {}, - ), - const SizedBox(height: NextSpacing.sm), - NextButton( - text: 'With Icon ${style.name} ${size.name}', - style: style, - size: size, - icon: const Icon(Icons.star, color: Colors.white, size: 18), - onTap: () {}, - ), - ], - ), - ); - } - } - - return sections; - } -} diff --git a/client/lib/open/widgets/navigation/dg_drawer.dart b/client/lib/open/widgets/navigation/dg_drawer.dart index bb6ebcf7..c7cd98ee 100644 --- a/client/lib/open/widgets/navigation/dg_drawer.dart +++ b/client/lib/open/widgets/navigation/dg_drawer.dart @@ -92,10 +92,6 @@ class DgDrawer extends HookConsumerWidget { } }, ), - _DrawerItemData( - label: "Testing Buttons", - route: const ButtonsTestingScreenRoute(), - ), ] .where( (item) => diff --git a/client/lib/router/routes.dart b/client/lib/router/routes.dart index 3247dab2..ca7ddcc4 100644 --- a/client/lib/router/routes.dart +++ b/client/lib/router/routes.dart @@ -13,7 +13,6 @@ import 'package:mobile/open/screens/instance/instance_screen.dart'; import 'package:mobile/open/screens/mfa/mfa_code_screen.dart'; import 'package:mobile/open/screens/process_qr_screen.dart'; import 'package:mobile/open/screens/scan_qr_screen.dart'; -import 'package:mobile/open/screens/testing/buttons_screen.dart'; import 'package:talker_flutter/talker_flutter.dart'; import '../logging.dart'; @@ -190,15 +189,3 @@ class BiometryFinishScreenRoute extends GoRouteData return BiometryFinishScreen(); } } - -@TypedGoRoute(path: "/testing/buttons") -@immutable -class ButtonsTestingScreenRoute extends GoRouteData - with $ButtonsTestingScreenRoute { - const ButtonsTestingScreenRoute(); - - @override - Widget build(BuildContext context, GoRouterState state) { - return const ButtonsTestingScreen(); - } -} diff --git a/client/lib/router/routes.g.dart b/client/lib/router/routes.g.dart index d84d419d..1b3fac66 100644 --- a/client/lib/router/routes.g.dart +++ b/client/lib/router/routes.g.dart @@ -21,7 +21,6 @@ List get $appRoutes => [ $biometrySetupScreenRoute, $biometrySetupFailedScreenRoute, $biometryFinishScreenRoute, - $buttonsTestingScreenRoute, ]; RouteBase get $processQrScreenRoute => GoRouteData.$route( @@ -431,30 +430,3 @@ mixin $BiometryFinishScreenRoute on GoRouteData { @override void replace(BuildContext context) => context.replace(location); } - -RouteBase get $buttonsTestingScreenRoute => GoRouteData.$route( - path: '/testing/buttons', - hasOverriddenOnExit: false, - factory: $ButtonsTestingScreenRoute._fromState, -); - -mixin $ButtonsTestingScreenRoute on GoRouteData { - static ButtonsTestingScreenRoute _fromState(GoRouterState state) => - const ButtonsTestingScreenRoute(); - - @override - String get location => GoRouteData.$location('/testing/buttons'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} From 052b42f6af29ccc45ca74e9f077fa100a06336d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Tue, 11 Aug 2026 12:52:09 +0200 Subject: [PATCH 50/65] widgets up --- client/assets/next/icons/arrow_big.svg | 3 + client/assets/next/icons/arrow_small.svg | 3 + client/assets/next/icons/biometric.svg | 3 + client/assets/next/icons/globe.svg | 3 + client/assets/next/icons/key.svg | 3 + client/assets/next/icons/lock_open.svg | 3 + client/assets/next/icons/mail.svg | 3 + client/assets/next/icons/mobile_lock.svg | 3 + client/assets/next/img/location_avatar.png | Bin 0 -> 3514 bytes .../next/img/location_connected_globe.png | Bin 0 -> 5133 bytes client/lib/data/db/enums.dart | 24 +- .../instance/widgets/mfa_method_dialog.dart | 6 +- .../widgets/routing_method_dialog.dart | 4 +- .../open/widgets/next/icons/next_icon.dart | 240 +++++++++++ .../open/widgets/next/next_bottom_sheet.dart | 187 ++++++++ client/lib/open/widgets/next/next_button.dart | 295 ++++++++++++- .../open/widgets/next/next_location_card.dart | 354 +++++++++++++++ .../widgets/next/next_text_form_field.dart | 406 ++++++++++++++++++ client/lib/theme/next/color.dart | 1 + client/pubspec.yaml | 8 +- client/test/next_icon_test.dart | 67 +++ 21 files changed, 1601 insertions(+), 15 deletions(-) create mode 100644 client/assets/next/icons/arrow_big.svg create mode 100644 client/assets/next/icons/arrow_small.svg create mode 100644 client/assets/next/icons/biometric.svg create mode 100644 client/assets/next/icons/globe.svg create mode 100644 client/assets/next/icons/key.svg create mode 100644 client/assets/next/icons/lock_open.svg create mode 100644 client/assets/next/icons/mail.svg create mode 100644 client/assets/next/icons/mobile_lock.svg create mode 100644 client/assets/next/img/location_avatar.png create mode 100644 client/assets/next/img/location_connected_globe.png create mode 100644 client/lib/open/widgets/next/icons/next_icon.dart create mode 100644 client/lib/open/widgets/next/next_bottom_sheet.dart create mode 100644 client/lib/open/widgets/next/next_location_card.dart create mode 100644 client/lib/open/widgets/next/next_text_form_field.dart create mode 100644 client/test/next_icon_test.dart diff --git a/client/assets/next/icons/arrow_big.svg b/client/assets/next/icons/arrow_big.svg new file mode 100644 index 00000000..4d36acf8 --- /dev/null +++ b/client/assets/next/icons/arrow_big.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/arrow_small.svg b/client/assets/next/icons/arrow_small.svg new file mode 100644 index 00000000..eb95302d --- /dev/null +++ b/client/assets/next/icons/arrow_small.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/biometric.svg b/client/assets/next/icons/biometric.svg new file mode 100644 index 00000000..5c7dac7e --- /dev/null +++ b/client/assets/next/icons/biometric.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/globe.svg b/client/assets/next/icons/globe.svg new file mode 100644 index 00000000..3664e1a5 --- /dev/null +++ b/client/assets/next/icons/globe.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/key.svg b/client/assets/next/icons/key.svg new file mode 100644 index 00000000..ca6c5ede --- /dev/null +++ b/client/assets/next/icons/key.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/lock_open.svg b/client/assets/next/icons/lock_open.svg new file mode 100644 index 00000000..c24d652c --- /dev/null +++ b/client/assets/next/icons/lock_open.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/mail.svg b/client/assets/next/icons/mail.svg new file mode 100644 index 00000000..bb8a771c --- /dev/null +++ b/client/assets/next/icons/mail.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/mobile_lock.svg b/client/assets/next/icons/mobile_lock.svg new file mode 100644 index 00000000..2523a883 --- /dev/null +++ b/client/assets/next/icons/mobile_lock.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/img/location_avatar.png b/client/assets/next/img/location_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..6d48bcb4a90a44eac4662bdfecc632d07af85517 GIT binary patch literal 3514 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 literal 0 HcmV?d00001 diff --git a/client/assets/next/img/location_connected_globe.png b/client/assets/next/img/location_connected_globe.png new file mode 100644 index 0000000000000000000000000000000000000000..0be577e5c7c8b4b930859dcc36be3dc7d381fb8e GIT binary patch literal 5133 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 { diff --git a/client/lib/open/screens/instance/widgets/mfa_method_dialog.dart b/client/lib/open/screens/instance/widgets/mfa_method_dialog.dart index 09a0e9b0..43cf2f44 100644 --- a/client/lib/open/screens/instance/widgets/mfa_method_dialog.dart +++ b/client/lib/open/screens/instance/widgets/mfa_method_dialog.dart @@ -90,21 +90,21 @@ class MfaMethodDialog extends HookConsumerWidget { children: [ if (instance.mfaKeysStored && biometricsStatus.canOpenStorage) DgRadioBox( - text: "Biometric", + text: MfaMethod.biometric.toUiString(), active: selectedMethod.value == MfaMethod.biometric, onTap: () { selectedMethod.value = MfaMethod.biometric; }, ), DgRadioBox( - text: "Authenticator App", + text: MfaMethod.totp.toUiString(), active: selectedMethod.value == MfaMethod.totp, onTap: () { selectedMethod.value = MfaMethod.totp; }, ), DgRadioBox( - text: "Email", + text: MfaMethod.email.toUiString(), active: selectedMethod.value == MfaMethod.email, onTap: () { selectedMethod.value = MfaMethod.email; diff --git a/client/lib/open/screens/instance/widgets/routing_method_dialog.dart b/client/lib/open/screens/instance/widgets/routing_method_dialog.dart index e64736e3..e6602ada 100644 --- a/client/lib/open/screens/instance/widgets/routing_method_dialog.dart +++ b/client/lib/open/screens/instance/widgets/routing_method_dialog.dart @@ -93,7 +93,7 @@ class RoutingMethodDialog extends HookConsumerWidget { spacing: DgSpacing.s, children: [ DgRadioBox( - text: "Predefined Traffic", + text: RoutingMethod.predefined.toUiString(), active: connectionType.value == RoutingMethod.predefined, onTap: () { connectionType.value = RoutingMethod.predefined; @@ -105,7 +105,7 @@ class RoutingMethodDialog extends HookConsumerWidget { ), DgSeparator(), DgRadioBox( - text: "All Traffic", + text: RoutingMethod.all.toUiString(), active: connectionType.value == RoutingMethod.all, onTap: () { connectionType.value = RoutingMethod.all; diff --git a/client/lib/open/widgets/next/icons/next_icon.dart b/client/lib/open/widgets/next/icons/next_icon.dart new file mode 100644 index 00000000..9191f806 --- /dev/null +++ b/client/lib/open/widgets/next/icons/next_icon.dart @@ -0,0 +1,240 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile/theme/next/color.dart'; + +/// Direction enum for Next design system icons. +enum NextIconDirection { right, down, left, up } + +typedef NextDirection = NextIconDirection; + +/// Icon renderer widget for Next design system icons. +/// +/// Automatically resolves icon asset names relative to `assets/next/icons/` +/// and appends `.svg` extension if omitted. Supports auto-rotation based on +/// icon [direction]. +class NextIcon extends StatelessWidget { + /// Global map specifying the base direction of icons. + /// Defaults to [NextIconDirection.right] if an icon is not present in this map. + static final Map baseDirections = { + 'arrow_big': NextIconDirection.right, + 'arrow_small': NextIconDirection.right, + }; + + /// Register or override the base direction for a specific icon asset name. + static void registerBaseDirection( + String name, + NextIconDirection baseDirection, + ) { + baseDirections[_cleanName(name)] = baseDirection; + } + + /// Register multiple icon base directions at once. + static void registerBaseDirections( + Map directions, + ) { + directions.forEach((key, value) { + baseDirections[_cleanName(key)] = value; + }); + } + + /// Helper to extract base icon name without path or `.svg` extension. + static String _cleanName(String rawName) { + var clean = rawName.trim(); + if (clean.contains('/')) { + clean = clean.split('/').last; + } + if (clean.endsWith('.svg')) { + clean = clean.substring(0, clean.length - 4); + } + return clean; + } + + /// Calculates the rotation angle in radians required to turn from [baseDirection] + /// to [targetDirection] clockwise. + static double getRotationForDirection( + NextIconDirection targetDirection, { + NextIconDirection baseDirection = NextIconDirection.right, + }) { + final steps = (targetDirection.index - baseDirection.index) % 4; + return steps * (math.pi / 2); + } + + /// Name or relative path of the icon asset (e.g., `'arrow_small'`). + final String name; + + /// Icon tint color. Defaults to [NextColor.fgAction]. + final Color color; + + /// Square size of the icon (width and height). Defaults to `20`. + final double size; + + /// Additional rotation angle in radians. Defaults to `0`. + final double rotation; + + /// Target direction of the icon. + /// + /// When provided, automatically calculates additional rotation angle based on + /// the icon's configured base direction. + final NextIconDirection? direction; + + const NextIcon( + this.name, { + super.key, + this.color = NextColor.fgAction, + this.size = 20, + this.rotation = 0, + this.direction, + }); + + /// Named constructor accepting `name` as a named argument. + const NextIcon.named({ + required String name, + Key? key, + Color color = NextColor.fgAction, + double size = 20, + double rotation = 0, + NextIconDirection? direction, + }) : this( + name, + key: key, + color: color, + size: size, + rotation: rotation, + direction: direction, + ); + + /// Constructs full asset path from asset name. + String get assetPath { + var path = name.trim(); + if (!path.startsWith('assets/')) { + path = 'assets/next/icons/$path'; + } + if (!path.endsWith('.svg')) { + path = '$path.svg'; + } + return path; + } + + /// Returns the configured base direction for this icon. + NextIconDirection get iconBaseDirection { + final clean = _cleanName(name); + return baseDirections[clean] ?? NextIconDirection.right; + } + + /// Calculates total effective rotation angle in radians. + double get effectiveRotation { + double total = rotation; + if (direction != null) { + total += getRotationForDirection( + direction!, + baseDirection: iconBaseDirection, + ); + } + return total; + } + + /// Creates a copy of this [NextIcon] with given fields replaced. + NextIcon copyWith({ + String? name, + Color? color, + double? size, + double? rotation, + NextIconDirection? direction, + }) { + return NextIcon( + name ?? this.name, + color: color ?? this.color, + size: size ?? this.size, + rotation: rotation ?? this.rotation, + direction: direction ?? this.direction, + ); + } + + @override + Widget build(BuildContext context) { + Widget iconWidget = SvgPicture.asset( + assetPath, + width: size, + height: size, + colorFilter: ColorFilter.mode(color, BlendMode.srcIn), + ); + + final angle = effectiveRotation; + if (angle != 0) { + iconWidget = Transform.rotate(angle: angle, child: iconWidget); + } + + return iconWidget; + } +} + +Widget _previewWrapper(Widget child) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment(-0.72, -0.69), + end: Alignment(0.72, 0.69), + colors: [Color(0xFF141517), Color(0xFF191A1C)], + ), + ), + padding: const EdgeInsets.all(24.0), + child: Center(child: child), + ); +} + +@Preview(name: 'Arrow Small Directions', group: 'NextIcon') +Widget previewArrowSmallDirections() { + return _previewWrapper( + Row( + mainAxisSize: MainAxisSize.min, + children: const [ + NextIcon('arrow_small', direction: NextIconDirection.right), + SizedBox(width: 16), + NextIcon('arrow_small', direction: NextIconDirection.down), + SizedBox(width: 16), + NextIcon('arrow_small', direction: NextIconDirection.left), + SizedBox(width: 16), + NextIcon('arrow_small', direction: NextIconDirection.up), + ], + ), + ); +} + +@Preview(name: 'Arrow Big Directions', group: 'NextIcon') +Widget previewArrowBigDirections() { + return _previewWrapper( + Row( + mainAxisSize: MainAxisSize.min, + children: const [ + NextIcon('arrow_big', direction: NextIconDirection.right), + SizedBox(width: 16), + NextIcon('arrow_big', direction: NextIconDirection.down), + SizedBox(width: 16), + NextIcon('arrow_big', direction: NextIconDirection.left), + SizedBox(width: 16), + NextIcon('arrow_big', direction: NextIconDirection.up), + ], + ), + ); +} + +@Preview(name: 'Arrow Colors & Sizes', group: 'NextIcon') +Widget previewArrowColorsAndSizes() { + return _previewWrapper( + Row( + mainAxisSize: MainAxisSize.min, + children: const [ + NextIcon('arrow_small', size: 16, color: NextColor.fgWhite100), + SizedBox(width: 16), + NextIcon('arrow_big', size: 20, color: NextColor.fgAction), + SizedBox(width: 16), + NextIcon('arrow_small', size: 24, color: NextColor.fgAttention), + SizedBox(width: 16), + NextIcon('arrow_big', size: 32, color: NextColor.fgCritical), + ], + ), + ); +} diff --git a/client/lib/open/widgets/next/next_bottom_sheet.dart b/client/lib/open/widgets/next/next_bottom_sheet.dart new file mode 100644 index 00000000..2a5e6aaf --- /dev/null +++ b/client/lib/open/widgets/next/next_bottom_sheet.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:mobile/theme/next/color.dart'; +import 'package:mobile/theme/next/spacing.dart'; + +/// A styled [BottomSheet] widget adhering to Next design specifications. +/// +/// Specs: +/// - Background color: [NextColor.bgDarkBlue80] (`rgba(0, 25, 137, 0.80)`) +/// - Top corner radius: 20 +/// - Content area padding: 8 top, 4 bottom (plus safe area bottom inset), 20 horizontal +/// - Top drag indicator header: 37 height with centered shape (50 width, 5 height, radius 100, color [NextColor.fgDisabled]) +class NextBottomSheet extends StatelessWidget { + /// The widget content inside the bottom sheet. + final Widget? child; + + /// A builder that creates the widget content inside the bottom sheet. + final WidgetBuilder? builder; + + /// Called when the bottom sheet is closed via dragging down. + final VoidCallback? onClosing; + + /// The animation controller that controls the bottom sheet's entrance and exit. + final AnimationController? animationController; + + /// Whether the bottom sheet can be dragged up and down and dismissed by swiping down. + final bool enableDrag; + + /// Whether to show the top drag handle header. Defaults to true. + final bool showDragHandle; + + /// The background color of the bottom sheet. Defaults to [NextColor.bgDarkBlue80]. + final Color? backgroundColor; + + /// The elevation of the bottom sheet. Defaults to 0. + final double? elevation; + + /// Optional override for content padding. If null, default specs are applied. + final EdgeInsetsGeometry? contentPadding; + + const NextBottomSheet({ + super.key, + this.child, + this.builder, + this.onClosing, + this.animationController, + this.enableDrag = true, + this.showDragHandle = true, + this.backgroundColor, + this.elevation, + this.contentPadding, + }) : assert( + child != null || builder != null, + 'Either child or builder must be provided.', + ); + + @override + Widget build(BuildContext context) { + final bottomSafeArea = MediaQuery.paddingOf(context).bottom; + + final effectivePadding = contentPadding != null + ? contentPadding!.add(EdgeInsets.only(bottom: bottomSafeArea)) + : EdgeInsets.only( + left: NextSpacing.xl, + right: NextSpacing.xl, + top: NextSpacing.sm, + bottom: NextSpacing.xs + bottomSafeArea, + ); + + return BottomSheet( + onClosing: onClosing ?? () {}, + animationController: animationController, + enableDrag: enableDrag, + backgroundColor: backgroundColor ?? NextColor.bgDarkBlue80, + elevation: elevation ?? 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + clipBehavior: Clip.antiAlias, + builder: (BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showDragHandle) + SizedBox( + height: 37, + child: Center( + child: Container( + width: 50, + height: 5, + decoration: BoxDecoration( + color: NextColor.fgDisabled, + borderRadius: BorderRadius.circular(100), + ), + ), + ), + ), + Padding( + padding: effectivePadding, + child: builder?.call(context) ?? child!, + ), + ], + ); + }, + ); + } +} + +/// Helper function to display a [NextBottomSheet] as a modal bottom sheet. +Future showNextBottomSheet({ + required BuildContext context, + WidgetBuilder? builder, + Widget? child, + bool isScrollControlled = false, + bool useRootNavigator = false, + bool isDismissible = true, + bool enableDrag = true, + bool showDragHandle = true, + Color? backgroundColor, + Color? barrierColor, + RouteSettings? routeSettings, + AnimationController? transitionAnimationController, + EdgeInsetsGeometry? contentPadding, +}) { + assert( + builder != null || child != null, + 'Either builder or child must be provided.', + ); + + return showModalBottomSheet( + context: context, + isScrollControlled: isScrollControlled, + useRootNavigator: useRootNavigator, + isDismissible: isDismissible, + enableDrag: enableDrag, + backgroundColor: Colors.transparent, + barrierColor: barrierColor, + routeSettings: routeSettings, + transitionAnimationController: transitionAnimationController, + builder: (modalContext) { + return NextBottomSheet( + enableDrag: enableDrag, + showDragHandle: showDragHandle, + backgroundColor: backgroundColor, + contentPadding: contentPadding, + onClosing: () { + if (Navigator.canPop(modalContext)) { + Navigator.pop(modalContext); + } + }, + builder: builder, + child: child, + ); + }, + ); +} + +@Preview(name: 'NextBottomSheet Default', group: 'NextBottomSheet') +Widget previewNextBottomSheetDefault() { + return Center( + child: NextBottomSheet( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'Add new instance', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + SizedBox(height: 16), + Text( + 'Scan QR code', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + SizedBox(height: 16), + Text( + 'Add manually', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ], + ), + ), + ); +} diff --git a/client/lib/open/widgets/next/next_button.dart b/client/lib/open/widgets/next/next_button.dart index 5df9dde8..b31c70d8 100644 --- a/client/lib/open/widgets/next/next_button.dart +++ b/client/lib/open/widgets/next/next_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; import 'package:mobile/open/widgets/next/next_circular_progress.dart'; import 'package:mobile/theme/next/color.dart'; import 'package:mobile/theme/next/spacing.dart'; @@ -69,13 +70,13 @@ class NextButton extends StatelessWidget { // Size variants switch (size) { case NextButtonSize.big: - heightInner = 44; + heightInner = height ?? 44; borderRadiusInner = BorderRadius.circular(100); paddingInner = NextSpacing.lg; textStyleInner = NextText.buttonLabelBig; break; case NextButtonSize.primary: - heightInner = 36; + heightInner = height ?? 36; borderRadiusInner = BorderRadius.circular(8); paddingInner = NextSpacing.lg; textStyleInner = NextText.buttonLabelPrimary; @@ -94,7 +95,7 @@ class NextButton extends StatelessWidget { backgroundColorInner = NextColor.bgWhite10; break; case NextButtonStyle.critical: - backgroundColorInner = Colors.red; + backgroundColorInner = NextColor.bgCritical; break; case NextButtonStyle.outlined: backgroundColorInner = Colors.transparent; @@ -199,3 +200,291 @@ class NextButton extends StatelessWidget { return children; } } + +@Preview(name: 'Normal', group: 'Big/Primary') +Widget previewBigPrimaryNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.primary, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Big/Primary') +Widget previewBigPrimaryLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.primary, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Big/Primary') +Widget previewBigPrimaryDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.primary, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Big/Secondary') +Widget previewBigSecondaryNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.secondary, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Big/Secondary') +Widget previewBigSecondaryLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.secondary, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Big/Secondary') +Widget previewBigSecondaryDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.secondary, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Big/Critical') +Widget previewBigCriticalNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.critical, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Big/Critical') +Widget previewBigCriticalLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.critical, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Big/Critical') +Widget previewBigCriticalDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.critical, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Big/Outlined') +Widget previewBigOutlinedNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.outlined, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Big/Outlined') +Widget previewBigOutlinedLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.outlined, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Big/Outlined') +Widget previewBigOutlinedDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.big, + style: NextButtonStyle.outlined, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Primary/Primary') +Widget previewPrimaryPrimaryNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.primary, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Primary/Primary') +Widget previewPrimaryPrimaryLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.primary, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Primary/Primary') +Widget previewPrimaryPrimaryDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.primary, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Primary/Secondary') +Widget previewPrimarySecondaryNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.secondary, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Primary/Secondary') +Widget previewPrimarySecondaryLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.secondary, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Primary/Secondary') +Widget previewPrimarySecondaryDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.secondary, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Primary/Critical') +Widget previewPrimaryCriticalNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.critical, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Primary/Critical') +Widget previewPrimaryCriticalLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.critical, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Primary/Critical') +Widget previewPrimaryCriticalDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.critical, + disabled: true, + ), + ); +} + +@Preview(name: 'Normal', group: 'Primary/Outlined') +Widget previewPrimaryOutlinedNormal() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.outlined, + onTap: () {}, + ), + ); +} + +@Preview(name: 'Loading', group: 'Primary/Outlined') +Widget previewPrimaryOutlinedLoading() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.outlined, + loading: true, + ), + ); +} + +@Preview(name: 'Disabled', group: 'Primary/Outlined') +Widget previewPrimaryOutlinedDisabled() { + return Center( + child: NextButton( + text: 'Button', + size: NextButtonSize.primary, + style: NextButtonStyle.outlined, + disabled: true, + ), + ); +} diff --git a/client/lib/open/widgets/next/next_location_card.dart b/client/lib/open/widgets/next/next_location_card.dart new file mode 100644 index 00000000..4bbed441 --- /dev/null +++ b/client/lib/open/widgets/next/next_location_card.dart @@ -0,0 +1,354 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:mobile/data/db/database.dart'; +import 'package:mobile/data/db/enums.dart'; +import 'package:mobile/open/widgets/next/icons/next_icon.dart'; +import 'package:mobile/open/widgets/next/next_button.dart'; +import 'package:mobile/theme/next/color.dart'; +import 'package:mobile/theme/next/spacing.dart'; +import 'package:mobile/theme/next/text.dart'; + +class NextLocationCard extends StatelessWidget { + final Location location; + final bool isConnected; + final bool loading; + final MfaMethod? mfaMethod; + final RoutingMethod? routingMethod; + final VoidCallback? onConnectTap; + final VoidCallback? onDisconnectTap; + + const NextLocationCard({ + super.key, + required this.location, + this.isConnected = false, + this.loading = false, + this.mfaMethod, + this.routingMethod, + this.onConnectTap, + this.onDisconnectTap, + }); + + @override + Widget build(BuildContext context) { + if (isConnected) { + return _buildConnectedLayout(context); + } else { + return _buildNotConnectedLayout(context); + } + } + + Widget _buildConnectedLayout(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: NextColor.bgDarkBlue20, + ), + padding: const EdgeInsets.all(NextSpacing.md), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + "assets/next/img/location_connected_globe.png", + width: 40, + height: 40, + semanticLabel: "Connected location globe", + ), + const SizedBox(width: NextSpacing.md), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Location", + style: NextText.bodyXs400.copyWith( + color: NextColor.fgWhite70, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: NextSpacing.sm, + children: [ + Text( + location.name, + style: NextText.bodyPrimary600.copyWith( + color: NextColor.fgWhite100, + ), + ), + Container( + padding: const EdgeInsets.symmetric( + vertical: 1, + horizontal: 4, + ), + decoration: BoxDecoration( + color: const Color(0xFF74FFB8), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + "Online", + style: NextText.bodyXxs600.copyWith( + color: const Color(0xFF2F50C2), + ), + ), + ), + ], + ), + ], + ), + ], + ), + const SizedBox(height: NextSpacing.md), + const Divider(height: 1, color: NextColor.bgWhite20), + const SizedBox(height: NextSpacing.md), + Row( + spacing: NextSpacing.md, + children: [ + if (routingMethod != null) + Expanded(child: _InnerInfoCard(routing: routingMethod)), + Expanded(child: _InnerInfoCard(mfaMethod: mfaMethod)), + ], + ), + const SizedBox(height: NextSpacing.lg), + NextButton( + text: "Disconnect", + onTap: onDisconnectTap, + size: NextButtonSize.big, + style: NextButtonStyle.outlined, + ), + ], + ), + ); + } + + Widget _buildNotConnectedLayout(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: NextColor.bgDarkBlue20, + ), + padding: const EdgeInsets.all(NextSpacing.sm), + child: Row( + spacing: NextSpacing.sm, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: 48, + height: 48, + child: Image.asset( + "assets/next/img/location_avatar.png", + width: 48, + height: 48, + fit: BoxFit.fill, + semanticLabel: "Location avatar", + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: NextSpacing.xs, + children: [ + Text( + "Location", + style: NextText.bodyXs400.copyWith( + color: NextColor.fgWhite40, + ), + ), + Text( + location.name, + style: NextText.bodySm500.copyWith( + color: NextColor.fgWhite100, + ), + ), + ], + ), + ), + NextButton( + text: "Connect", + onTap: onConnectTap, + size: NextButtonSize.big, + style: NextButtonStyle.secondary, + height: 36, + ), + ], + ), + ); + } +} + +@Preview(name: 'Connected', group: 'NextLocationCard') +Widget previewConnected() { + return _PreviewWrapper( + child: NextLocationCard(isConnected: true, location: _mockLocation()), + ); +} + +@Preview(name: 'Not Connected', group: 'NextLocationCard') +Widget previewNotConnected() { + return _PreviewWrapper( + child: NextLocationCard(isConnected: false, location: _mockLocation()), + ); +} + +@Preview(name: 'Loading', group: 'NextLocationCard') +Widget previewLoading() { + return _PreviewWrapper( + child: NextLocationCard( + isConnected: false, + loading: true, + location: _mockLocation(), + ), + ); +} + +class _InnerInfoCard extends StatelessWidget { + final RoutingMethod? routing; + final MfaMethod? mfaMethod; + + const _InnerInfoCard({this.routing, this.mfaMethod}); + + bool get isMfa => mfaMethod != null; + bool get isRouting => routing != null; + bool get isEmpty => mfaMethod == null && routing == null; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: NextSpacing.sm, + horizontal: NextSpacing.md, + ), + decoration: BoxDecoration( + color: NextColor.bgWhite5, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: NextSpacing.md, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: NextColor.bgWhite10, + ), + width: 36, + height: 36, + alignment: Alignment.center, + child: getIcon(), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 2, + children: [ + Text( + getLabel(), + style: NextText.bodyXxs400.copyWith(color: NextColor.fgWhite50), + ), + Text( + getText(), + style: NextText.bodyXs500.copyWith(color: getTextColor()), + ), + ], + ), + ], + ), + ); + } + + Widget getIcon() { + Color iconColor = NextColor.fgWhite100; + String iconFileName = "lock_open"; + if (isEmpty) { + iconFileName = "lock_open"; + iconColor = NextColor.fgWhite60; + } else if (isRouting) { + iconFileName = "globe"; + } else if (isMfa) { + switch (mfaMethod) { + case .totp: + iconFileName = "mobile_lock"; + break; + case .biometric: + iconFileName = "biometric"; + break; + case .email: + iconFileName = "email"; + break; + case .openid: + iconFileName = "key"; + break; + default: + iconFileName = "mobile_lock"; + } + } + return NextIcon(iconFileName, size: 20, color: iconColor); + } + + String getLabel() { + if (isMfa) { + return "MFA"; + } + if (isRouting) { + return "Traffic"; + } + return "MFA"; + } + + String getText() { + if (isMfa) { + return mfaMethod?.toUiString() ?? "MFA"; + } + if (isRouting) { + return routing?.toUiString() ?? "Traffic"; + } + return "Not required"; + } + + Color getTextColor() { + if (isEmpty) { + return NextColor.fgWhite60; + } + return NextColor.fgWhite100; + } +} + +class _PreviewWrapper extends StatelessWidget { + final Widget child; + const _PreviewWrapper({required this.child}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment(-0.72, -0.69), + end: Alignment(0.72, 0.69), + colors: [Color(0xFF141517), Color(0xFF191A1C)], + ), + ), + padding: const EdgeInsets.all(24.0), + child: Center(child: child), + ); + } +} + +Location _mockLocation({bool mfaEnabled = false}) { + return Location( + id: 1, + instance: 1, + networkId: 1, + name: 'Warsaw Office', + address: '10.0.0.1/24', + pubKey: 'pubkey', + endpoint: 'vpn.example.com:51820', + allowedIps: '0.0.0.0/0', + keepAliveInterval: 25, + mfaEnabled: mfaEnabled, + locationMfaMode: mfaEnabled + ? LocationMfaMode.internal + : LocationMfaMode.unspecified, + ); +} diff --git a/client/lib/open/widgets/next/next_text_form_field.dart b/client/lib/open/widgets/next/next_text_form_field.dart new file mode 100644 index 00000000..34de833d --- /dev/null +++ b/client/lib/open/widgets/next/next_text_form_field.dart @@ -0,0 +1,406 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:mobile/theme/next/color.dart'; +import 'package:mobile/theme/next/spacing.dart'; +import 'package:mobile/theme/next/text.dart'; + +enum NextTextFormFieldSize { primary, big } + +class NextTextFormField extends FormField { + final NextTextFormFieldSize size; + final String? label; + final String? hintText; + final String? errorText; + final TextEditingController? controller; + final FocusNode? focusNode; + final bool obscureText; + final TextInputType keyboardType; + final bool required; + final int maxLines; + final bool disabled; + final bool readOnly; + final ValueChanged? onChanged; + final ValueChanged? onFieldSubmitted; + + NextTextFormField({ + super.key, + this.size = NextTextFormFieldSize.primary, + this.label, + this.hintText, + this.errorText, + this.controller, + this.focusNode, + super.autovalidateMode = AutovalidateMode.onUserInteraction, + this.obscureText = false, + this.keyboardType = TextInputType.text, + this.required = false, + this.maxLines = 1, + super.validator, + this.disabled = false, + this.readOnly = false, + this.onChanged, + this.onFieldSubmitted, + super.onSaved, + String? initialValue, + }) : assert( + initialValue == null || controller == null, + 'If controller is specified, initialValue must be null.', + ), + super( + initialValue: controller != null + ? controller.text + : (initialValue ?? ''), + enabled: !disabled, + builder: (FormFieldState field) { + return _NextTextFormFieldContent( + state: field, + size: size, + label: label, + hintText: hintText, + errorText: errorText, + controller: controller, + focusNode: focusNode, + obscureText: obscureText, + keyboardType: keyboardType, + required: required, + maxLines: maxLines, + disabled: disabled, + readOnly: readOnly, + onChanged: onChanged, + onFieldSubmitted: onFieldSubmitted, + ); + }, + ); +} + +class _NextTextFormFieldContent extends HookWidget { + final FormFieldState state; + final NextTextFormFieldSize size; + final String? label; + final String? hintText; + final String? errorText; + final TextEditingController? controller; + final FocusNode? focusNode; + final bool obscureText; + final TextInputType keyboardType; + final bool required; + final int maxLines; + final bool disabled; + final bool readOnly; + final ValueChanged? onChanged; + final ValueChanged? onFieldSubmitted; + + const _NextTextFormFieldContent({ + required this.state, + required this.size, + this.label, + this.hintText, + this.errorText, + this.controller, + this.focusNode, + required this.obscureText, + required this.keyboardType, + required this.required, + required this.maxLines, + required this.disabled, + required this.readOnly, + this.onChanged, + this.onFieldSubmitted, + }); + + @override + Widget build(BuildContext context) { + final effectiveFocusNode = focusNode ?? useFocusNode(); + final effectiveController = + controller ?? useTextEditingController(text: state.value ?? ''); + + final isFocused = useState(effectiveFocusNode.hasFocus); + + useEffect(() { + void handleFocusChange() { + if (isFocused.value != effectiveFocusNode.hasFocus) { + isFocused.value = effectiveFocusNode.hasFocus; + } + } + + effectiveFocusNode.addListener(handleFocusChange); + return () => effectiveFocusNode.removeListener(handleFocusChange); + }, [effectiveFocusNode]); + + // Sync controller changes -> FormFieldState + useEffect(() { + void handleControllerChange() { + if (state.value != effectiveController.text) { + state.didChange(effectiveController.text); + } + } + + effectiveController.addListener(handleControllerChange); + return () => effectiveController.removeListener(handleControllerChange); + }, [effectiveController, state]); + + // Sync FormFieldState -> controller (e.g. FormState.reset()) + useEffect(() { + if (effectiveController.text != (state.value ?? '')) { + effectiveController.text = state.value ?? ''; + } + return null; + }, [state.value]); + + final String? effectiveError = disabled + ? null + : (errorText ?? state.errorText); + final bool hasError = effectiveError != null && effectiveError.isNotEmpty; + + final Color borderColor; + if (disabled) { + borderColor = NextColor.borderDisabled; + } else if (hasError) { + borderColor = NextColor.borderCritical; + } else if (isFocused.value) { + borderColor = NextColor.borderEmphasis; + } else { + borderColor = NextColor.borderDefault; + } + + final Color backgroundColor = disabled + ? NextColor.bgWhite10 + : Colors.transparent; + + final TextStyle baseTextStyle = size == NextTextFormFieldSize.big + ? NextText.inputBig + : NextText.inputPrimary; + final TextStyle inputTextStyle = baseTextStyle.copyWith( + color: disabled ? NextColor.fgWhite50 : NextColor.fgWhite100, + ); + final TextStyle hintTextStyle = baseTextStyle.copyWith( + color: NextColor.fgWhite50, + ); + + final EdgeInsets padding = size == NextTextFormFieldSize.big + ? const EdgeInsets.symmetric( + vertical: NextSpacing.sm, + horizontal: NextSpacing.xl, + ) + : const EdgeInsets.symmetric( + vertical: NextSpacing.sm, + horizontal: NextSpacing.md, + ); + + final BorderRadius borderRadius = size == NextTextFormFieldSize.big + ? BorderRadius.circular(100) + : BorderRadius.circular(8); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (label != null && label!.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (required) ...[ + Text( + '*', + style: NextText.inputTitle.copyWith( + color: NextColor.fgWhite60, + ), + ), + const SizedBox(width: NextSpacing.xs), + ], + Text( + label!, + style: NextText.inputTitle.copyWith( + color: NextColor.fgWhite80, + ), + ), + ], + ), + ), + const SizedBox(height: NextSpacing.xs), + ], + GestureDetector( + onTap: effectiveFocusNode.requestFocus, + behavior: HitTestBehavior.opaque, + child: AnimatedContainer( + duration: const Duration(milliseconds: 160), + curve: Curves.easeOut, + padding: padding, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius, + border: Border.all(color: borderColor, width: 1.0), + ), + child: TextField( + controller: effectiveController, + focusNode: effectiveFocusNode, + enabled: !disabled, + readOnly: readOnly, + obscureText: obscureText, + keyboardType: keyboardType, + maxLines: maxLines, + onChanged: (value) { + onChanged?.call(value); + }, + onSubmitted: onFieldSubmitted, + style: inputTextStyle, + cursorColor: NextColor.fgWhite100, + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + hintText: hintText, + hintStyle: hintTextStyle, + ), + ), + ), + ), + AnimatedSize( + duration: const Duration(milliseconds: 160), + curve: Curves.easeOut, + alignment: Alignment.topLeft, + child: (hasError) + ? Padding( + padding: const EdgeInsets.only(top: NextSpacing.sm), + child: Text( + effectiveError, + style: NextText.inputError.copyWith( + color: NextColor.bgCriticalMuted, + ), + ), + ) + : const SizedBox.shrink(), + ), + ], + ); + } +} + +Widget _previewWrapper(Widget child) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment(-0.72, -0.69), + end: Alignment(0.72, 0.69), + colors: [Color(0xFF5B83FF), Color(0xFF0036DB)], + ), + ), + padding: const EdgeInsets.all(24.0), + child: Center(child: SizedBox(width: 320, child: child)), + ); +} + +@Preview(name: 'Default', group: 'Primary') +Widget previewPrimaryDefault() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.primary, + hintText: 'Enter text...', + ), + ); +} + +@Preview(name: 'With Label & Required', group: 'Primary') +Widget previewPrimaryWithLabel() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.primary, + label: 'Email address', + required: true, + hintText: 'name@example.com', + ), + ); +} + +@Preview(name: 'With Text', group: 'Primary') +Widget previewPrimaryWithText() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.primary, + label: 'Username', + controller: TextEditingController(text: 'antigravity_user'), + ), + ); +} + +@Preview(name: 'Disabled', group: 'Primary') +Widget previewPrimaryDisabled() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.primary, + label: 'Role', + controller: TextEditingController(text: 'Administrator'), + disabled: true, + ), + ); +} + +@Preview(name: 'With Error', group: 'Primary') +Widget previewPrimaryWithError() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.primary, + label: 'Password', + required: true, + obscureText: true, + controller: TextEditingController(text: '123'), + errorText: 'Password must be at least 8 characters long', + ), + ); +} + +@Preview(name: 'Default', group: 'Big') +Widget previewBigDefault() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.big, + hintText: 'Search location...', + ), + ); +} + +@Preview(name: 'With Label & Required', group: 'Big') +Widget previewBigWithLabel() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.big, + label: 'Server Name', + required: true, + hintText: 'e.g. US-East-1', + ), + ); +} + +@Preview(name: 'Disabled', group: 'Big') +Widget previewBigDisabled() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.big, + label: 'Server Name', + controller: TextEditingController(text: 'Production Server'), + disabled: true, + ), + ); +} + +@Preview(name: 'With Error', group: 'Big') +Widget previewBigWithError() { + return _previewWrapper( + NextTextFormField( + size: NextTextFormFieldSize.big, + label: 'Port', + required: true, + controller: TextEditingController(text: '99999'), + errorText: 'Port number must be between 1 and 65535', + ), + ); +} diff --git a/client/lib/theme/next/color.dart b/client/lib/theme/next/color.dart index 50c23709..016a6d7a 100644 --- a/client/lib/theme/next/color.dart +++ b/client/lib/theme/next/color.dart @@ -139,6 +139,7 @@ class NextColor { // Background Colors - Semantic static const Color bgCritical = _Primitive.saturatedRed500; static const Color bgNeutral = _Primitive.saturatedBlueNeutral; + static const Color bgDarkBlue80 = _Primitive.saturatedDarkBlue80; static const Color bgDarkBlue60 = _Primitive.saturatedDarkBlue60; static const Color bgDarkBlue40 = _Primitive.saturatedDarkBlue40; static const Color bgDarkBlue30 = _Primitive.saturatedDarkBlue30; diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 70769e21..6fb3bb01 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -67,7 +67,6 @@ dependencies: url_launcher: ^6.3.2 uuid: ^4.5.1 flutter_local_notifications: ^22.2.0 - # sqlite3_flutter_libs version 0.5.37 bumps minimum compatible iOS version sqlite3_flutter_libs: ^0.5.42 x25519: ^0.1.1 ed25519_edwards: ^0.3.1 @@ -83,11 +82,6 @@ dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^6.0.0 go_router_builder: ^4.4.0 build_runner: ^2.15.1 @@ -110,6 +104,8 @@ flutter: assets: - assets/icons/ + - assets/next/icons/ + - assets/next/img/ fonts: - family: Roboto diff --git a/client/test/next_icon_test.dart b/client/test/next_icon_test.dart new file mode 100644 index 00000000..1ff5ce17 --- /dev/null +++ b/client/test/next_icon_test.dart @@ -0,0 +1,67 @@ +import 'dart:math' as math; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mobile/open/widgets/next/icons/next_icon.dart'; +import 'package:mobile/theme/next/color.dart'; + +void main() { + group('NextIcon', () { + test('constructs asset path correctly', () { + const icon1 = NextIcon('arrow_small'); + expect(icon1.assetPath, equals('assets/next/icons/arrow_small.svg')); + + const icon2 = NextIcon('arrow_small.svg'); + expect(icon2.assetPath, equals('assets/next/icons/arrow_small.svg')); + + const icon3 = NextIcon('assets/next/icons/arrow_small.svg'); + expect(icon3.assetPath, equals('assets/next/icons/arrow_small.svg')); + }); + + test('default properties are set correctly', () { + const icon = NextIcon('check'); + expect(icon.size, equals(20.0)); + expect(icon.color, equals(NextColor.fgAction)); + expect(icon.rotation, equals(0.0)); + expect(icon.direction, isNull); + expect(icon.effectiveRotation, equals(0.0)); + }); + + test('calculates direction rotation clockwise from base direction (right)', () { + const iconRight = NextIcon('arrow_small', direction: NextIconDirection.right); + expect(iconRight.effectiveRotation, equals(0.0)); + + const iconDown = NextIcon('arrow_small', direction: NextIconDirection.down); + expect(iconDown.effectiveRotation, closeTo(math.pi / 2, 0.0001)); // 90 degrees + + const iconLeft = NextIcon('arrow_small', direction: NextIconDirection.left); + expect(iconLeft.effectiveRotation, closeTo(math.pi, 0.0001)); // 180 degrees + + const iconUp = NextIcon('arrow_small', direction: NextIconDirection.up); + expect(iconUp.effectiveRotation, closeTo(3 * math.pi / 2, 0.0001)); // 270 degrees + }); + + test('arrow_big rotation works for all directions', () { + const iconDown = NextIcon('arrow_big', direction: NextIconDirection.down); + expect(iconDown.effectiveRotation, closeTo(math.pi / 2, 0.0001)); + }); + + test('static base direction registration works correctly', () { + NextIcon.registerBaseDirection('chevron', NextIconDirection.up); + + const iconUp = NextIcon('chevron', direction: NextIconDirection.up); + expect(iconUp.effectiveRotation, equals(0.0)); + + const iconRight = NextIcon('chevron', direction: NextIconDirection.right); + expect(iconRight.effectiveRotation, closeTo(math.pi / 2, 0.0001)); + }); + + test('copyWith updates direction property correctly', () { + const icon = NextIcon('arrow_small'); + final updated = icon.copyWith( + direction: NextIconDirection.down, + ); + + expect(updated.direction, equals(NextIconDirection.down)); + expect(updated.effectiveRotation, closeTo(math.pi / 2, 0.0001)); + }); + }); +} From 54b0940802bae0cfdc3e8f4e240361111c5cf258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Tue, 11 Aug 2026 16:27:49 +0200 Subject: [PATCH 51/65] widgets up --- .../assets/next/icons/connected_devices.svg | 3 + client/assets/next/icons/device_ip.svg | 3 + .../open/widgets/next/icons/next_icon.dart | 90 +++++++----- .../open/widgets/next/next_bottom_sheet.dart | 24 +-- client/lib/open/widgets/next/next_button.dart | 49 +++--- .../open/widgets/next/next_instance_card.dart | 139 ++++++++++++++++++ .../open/widgets/next/next_location_card.dart | 96 ++++++++---- .../lib/open/widgets/next/next_main_cta.dart | 78 ++++++++++ .../widgets/next/next_preview_wrapper.dart | 26 ++++ .../widgets/next/next_text_form_field.dart | 60 ++++---- client/lib/theme/next/color.dart | 12 ++ client/lib/theme/next/text.dart | 12 +- 12 files changed, 445 insertions(+), 147 deletions(-) create mode 100644 client/assets/next/icons/connected_devices.svg create mode 100644 client/assets/next/icons/device_ip.svg create mode 100644 client/lib/open/widgets/next/next_instance_card.dart create mode 100644 client/lib/open/widgets/next/next_main_cta.dart create mode 100644 client/lib/open/widgets/next/next_preview_wrapper.dart diff --git a/client/assets/next/icons/connected_devices.svg b/client/assets/next/icons/connected_devices.svg new file mode 100644 index 00000000..0628cbb3 --- /dev/null +++ b/client/assets/next/icons/connected_devices.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/assets/next/icons/device_ip.svg b/client/assets/next/icons/device_ip.svg new file mode 100644 index 00000000..84f418c3 --- /dev/null +++ b/client/assets/next/icons/device_ip.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/lib/open/widgets/next/icons/next_icon.dart b/client/lib/open/widgets/next/icons/next_icon.dart index 9191f806..1fba189d 100644 --- a/client/lib/open/widgets/next/icons/next_icon.dart +++ b/client/lib/open/widgets/next/icons/next_icon.dart @@ -5,6 +5,8 @@ import 'package:flutter/widget_previews.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile/theme/next/color.dart'; +import '../next_preview_wrapper.dart'; + /// Direction enum for Next design system icons. enum NextIconDirection { right, down, left, up } @@ -98,13 +100,13 @@ class NextIcon extends StatelessWidget { double rotation = 0, NextIconDirection? direction, }) : this( - name, - key: key, - color: color, - size: size, - rotation: rotation, - direction: direction, - ); + name, + key: key, + color: color, + size: size, + rotation: rotation, + direction: direction, + ); /// Constructs full asset path from asset name. String get assetPath { @@ -171,33 +173,35 @@ class NextIcon extends StatelessWidget { } } -Widget _previewWrapper(Widget child) { - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment(-0.72, -0.69), - end: Alignment(0.72, 0.69), - colors: [Color(0xFF141517), Color(0xFF191A1C)], - ), - ), - padding: const EdgeInsets.all(24.0), - child: Center(child: child), - ); -} - @Preview(name: 'Arrow Small Directions', group: 'NextIcon') Widget previewArrowSmallDirections() { - return _previewWrapper( - Row( + return NextPreviewWrapper( + child: Row( mainAxisSize: MainAxisSize.min, children: const [ - NextIcon('arrow_small', direction: NextIconDirection.right), + NextIcon( + 'arrow_small', + direction: NextIconDirection.right, + color: NextColor.fgWhite100, + ), SizedBox(width: 16), - NextIcon('arrow_small', direction: NextIconDirection.down), + NextIcon( + 'arrow_small', + direction: NextIconDirection.down, + color: NextColor.fgWhite100, + ), SizedBox(width: 16), - NextIcon('arrow_small', direction: NextIconDirection.left), + NextIcon( + 'arrow_small', + direction: NextIconDirection.left, + color: NextColor.fgWhite100, + ), SizedBox(width: 16), - NextIcon('arrow_small', direction: NextIconDirection.up), + NextIcon( + 'arrow_small', + direction: NextIconDirection.up, + color: NextColor.fgWhite100, + ), ], ), ); @@ -205,17 +209,33 @@ Widget previewArrowSmallDirections() { @Preview(name: 'Arrow Big Directions', group: 'NextIcon') Widget previewArrowBigDirections() { - return _previewWrapper( - Row( + return NextPreviewWrapper( + child: Row( mainAxisSize: MainAxisSize.min, children: const [ - NextIcon('arrow_big', direction: NextIconDirection.right), + NextIcon( + 'arrow_big', + direction: NextIconDirection.right, + color: NextColor.fgWhite100, + ), SizedBox(width: 16), - NextIcon('arrow_big', direction: NextIconDirection.down), + NextIcon( + 'arrow_big', + direction: NextIconDirection.down, + color: NextColor.fgWhite100, + ), SizedBox(width: 16), - NextIcon('arrow_big', direction: NextIconDirection.left), + NextIcon( + 'arrow_big', + direction: NextIconDirection.left, + color: NextColor.fgWhite100, + ), SizedBox(width: 16), - NextIcon('arrow_big', direction: NextIconDirection.up), + NextIcon( + 'arrow_big', + direction: NextIconDirection.up, + color: NextColor.fgWhite100, + ), ], ), ); @@ -223,8 +243,8 @@ Widget previewArrowBigDirections() { @Preview(name: 'Arrow Colors & Sizes', group: 'NextIcon') Widget previewArrowColorsAndSizes() { - return _previewWrapper( - Row( + return NextPreviewWrapper( + child: Row( mainAxisSize: MainAxisSize.min, children: const [ NextIcon('arrow_small', size: 16, color: NextColor.fgWhite100), diff --git a/client/lib/open/widgets/next/next_bottom_sheet.dart b/client/lib/open/widgets/next/next_bottom_sheet.dart index 2a5e6aaf..4c23acd5 100644 --- a/client/lib/open/widgets/next/next_bottom_sheet.dart +++ b/client/lib/open/widgets/next/next_bottom_sheet.dart @@ -1,41 +1,26 @@ import 'package:flutter/material.dart'; import 'package:flutter/widget_previews.dart'; +import 'package:mobile/open/widgets/next/next_preview_wrapper.dart'; import 'package:mobile/theme/next/color.dart'; import 'package:mobile/theme/next/spacing.dart'; -/// A styled [BottomSheet] widget adhering to Next design specifications. -/// -/// Specs: -/// - Background color: [NextColor.bgDarkBlue80] (`rgba(0, 25, 137, 0.80)`) -/// - Top corner radius: 20 -/// - Content area padding: 8 top, 4 bottom (plus safe area bottom inset), 20 horizontal -/// - Top drag indicator header: 37 height with centered shape (50 width, 5 height, radius 100, color [NextColor.fgDisabled]) class NextBottomSheet extends StatelessWidget { - /// The widget content inside the bottom sheet. final Widget? child; - /// A builder that creates the widget content inside the bottom sheet. final WidgetBuilder? builder; - /// Called when the bottom sheet is closed via dragging down. final VoidCallback? onClosing; - /// The animation controller that controls the bottom sheet's entrance and exit. final AnimationController? animationController; - /// Whether the bottom sheet can be dragged up and down and dismissed by swiping down. final bool enableDrag; - /// Whether to show the top drag handle header. Defaults to true. final bool showDragHandle; - /// The background color of the bottom sheet. Defaults to [NextColor.bgDarkBlue80]. final Color? backgroundColor; - /// The elevation of the bottom sheet. Defaults to 0. final double? elevation; - /// Optional override for content padding. If null, default specs are applied. final EdgeInsetsGeometry? contentPadding; const NextBottomSheet({ @@ -74,9 +59,7 @@ class NextBottomSheet extends StatelessWidget { backgroundColor: backgroundColor ?? NextColor.bgDarkBlue80, elevation: elevation ?? 0, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), clipBehavior: Clip.antiAlias, builder: (BuildContext context) { @@ -109,7 +92,6 @@ class NextBottomSheet extends StatelessWidget { } } -/// Helper function to display a [NextBottomSheet] as a modal bottom sheet. Future showNextBottomSheet({ required BuildContext context, WidgetBuilder? builder, @@ -160,7 +142,7 @@ Future showNextBottomSheet({ @Preview(name: 'NextBottomSheet Default', group: 'NextBottomSheet') Widget previewNextBottomSheetDefault() { - return Center( + return NextPreviewWrapper( child: NextBottomSheet( child: Column( mainAxisSize: MainAxisSize.min, diff --git a/client/lib/open/widgets/next/next_button.dart b/client/lib/open/widgets/next/next_button.dart index b31c70d8..8375cfbe 100644 --- a/client/lib/open/widgets/next/next_button.dart +++ b/client/lib/open/widgets/next/next_button.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widget_previews.dart'; import 'package:mobile/open/widgets/next/next_circular_progress.dart'; +import 'package:mobile/open/widgets/next/next_preview_wrapper.dart'; import 'package:mobile/theme/next/color.dart'; import 'package:mobile/theme/next/spacing.dart'; import 'package:mobile/theme/next/text.dart'; @@ -203,7 +204,7 @@ class NextButton extends StatelessWidget { @Preview(name: 'Normal', group: 'Big/Primary') Widget previewBigPrimaryNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -215,7 +216,7 @@ Widget previewBigPrimaryNormal() { @Preview(name: 'Loading', group: 'Big/Primary') Widget previewBigPrimaryLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -227,7 +228,7 @@ Widget previewBigPrimaryLoading() { @Preview(name: 'Disabled', group: 'Big/Primary') Widget previewBigPrimaryDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -239,7 +240,7 @@ Widget previewBigPrimaryDisabled() { @Preview(name: 'Normal', group: 'Big/Secondary') Widget previewBigSecondaryNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -251,7 +252,7 @@ Widget previewBigSecondaryNormal() { @Preview(name: 'Loading', group: 'Big/Secondary') Widget previewBigSecondaryLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -263,7 +264,7 @@ Widget previewBigSecondaryLoading() { @Preview(name: 'Disabled', group: 'Big/Secondary') Widget previewBigSecondaryDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -275,7 +276,7 @@ Widget previewBigSecondaryDisabled() { @Preview(name: 'Normal', group: 'Big/Critical') Widget previewBigCriticalNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -287,7 +288,7 @@ Widget previewBigCriticalNormal() { @Preview(name: 'Loading', group: 'Big/Critical') Widget previewBigCriticalLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -299,7 +300,7 @@ Widget previewBigCriticalLoading() { @Preview(name: 'Disabled', group: 'Big/Critical') Widget previewBigCriticalDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -311,7 +312,7 @@ Widget previewBigCriticalDisabled() { @Preview(name: 'Normal', group: 'Big/Outlined') Widget previewBigOutlinedNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -323,7 +324,7 @@ Widget previewBigOutlinedNormal() { @Preview(name: 'Loading', group: 'Big/Outlined') Widget previewBigOutlinedLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -335,7 +336,7 @@ Widget previewBigOutlinedLoading() { @Preview(name: 'Disabled', group: 'Big/Outlined') Widget previewBigOutlinedDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.big, @@ -347,7 +348,7 @@ Widget previewBigOutlinedDisabled() { @Preview(name: 'Normal', group: 'Primary/Primary') Widget previewPrimaryPrimaryNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -359,7 +360,7 @@ Widget previewPrimaryPrimaryNormal() { @Preview(name: 'Loading', group: 'Primary/Primary') Widget previewPrimaryPrimaryLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -371,7 +372,7 @@ Widget previewPrimaryPrimaryLoading() { @Preview(name: 'Disabled', group: 'Primary/Primary') Widget previewPrimaryPrimaryDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -383,7 +384,7 @@ Widget previewPrimaryPrimaryDisabled() { @Preview(name: 'Normal', group: 'Primary/Secondary') Widget previewPrimarySecondaryNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -395,7 +396,7 @@ Widget previewPrimarySecondaryNormal() { @Preview(name: 'Loading', group: 'Primary/Secondary') Widget previewPrimarySecondaryLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -407,7 +408,7 @@ Widget previewPrimarySecondaryLoading() { @Preview(name: 'Disabled', group: 'Primary/Secondary') Widget previewPrimarySecondaryDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -419,7 +420,7 @@ Widget previewPrimarySecondaryDisabled() { @Preview(name: 'Normal', group: 'Primary/Critical') Widget previewPrimaryCriticalNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -431,7 +432,7 @@ Widget previewPrimaryCriticalNormal() { @Preview(name: 'Loading', group: 'Primary/Critical') Widget previewPrimaryCriticalLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -443,7 +444,7 @@ Widget previewPrimaryCriticalLoading() { @Preview(name: 'Disabled', group: 'Primary/Critical') Widget previewPrimaryCriticalDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -455,7 +456,7 @@ Widget previewPrimaryCriticalDisabled() { @Preview(name: 'Normal', group: 'Primary/Outlined') Widget previewPrimaryOutlinedNormal() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -467,7 +468,7 @@ Widget previewPrimaryOutlinedNormal() { @Preview(name: 'Loading', group: 'Primary/Outlined') Widget previewPrimaryOutlinedLoading() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, @@ -479,7 +480,7 @@ Widget previewPrimaryOutlinedLoading() { @Preview(name: 'Disabled', group: 'Primary/Outlined') Widget previewPrimaryOutlinedDisabled() { - return Center( + return NextPreviewWrapper( child: NextButton( text: 'Button', size: NextButtonSize.primary, diff --git a/client/lib/open/widgets/next/next_instance_card.dart b/client/lib/open/widgets/next/next_instance_card.dart new file mode 100644 index 00000000..5817209e --- /dev/null +++ b/client/lib/open/widgets/next/next_instance_card.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:mobile/open/widgets/next/next_preview_wrapper.dart'; +import 'package:mobile/theme/next/color.dart'; +import 'package:mobile/theme/next/spacing.dart'; +import 'package:mobile/theme/next/text.dart'; + +import 'icons/next_icon.dart'; + +class NextInstanceCard extends StatelessWidget { + final int locationsCount; + final int connectedCount; + final String name; + final VoidCallback? onTap; + + const NextInstanceCard({ + super.key, + required this.locationsCount, + required this.connectedCount, + required this.name, + this.onTap, + }); + + bool get isConnected => connectedCount != 0; + String get icon => isConnected ? "connected_devices" : "device_ip"; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: .directional(start: 8, top: 8, end: 12, bottom: 8), + decoration: BoxDecoration( + borderRadius: .circular(20), + color: NextColor.bgDarkBlue20, + ), + child: Row( + crossAxisAlignment: .center, + mainAxisAlignment: .start, + children: [ + Container( + height: 48, + width: 48, + alignment: .center, + decoration: BoxDecoration( + borderRadius: .circular(14), + color: isConnected + ? NextColor.bgWhite100 + : NextColor.bgDarkBlue20, + ), + child: NextIcon( + icon, + size: 24, + color: isConnected ? NextColor.fgAction : NextColor.fgWhite60, + ), + ), + SizedBox(width: NextSpacing.xl), + Expanded( + child: Column( + crossAxisAlignment: .start, + children: [ + Text( + name, + style: NextText.bodyPrimary600.copyWith( + color: NextColor.fgWhite100, + ), + ), + Row( + crossAxisAlignment: .center, + mainAxisAlignment: .start, + spacing: 8, + children: [ + if (locationsCount == 0) + Text( + "No locations available", + style: NextText.bodyXs400.copyWith( + color: NextColor.fgWhite60, + ), + ), + if (locationsCount > 0) + Text( + "$locationsCount locations", + style: NextText.bodyXs400.copyWith( + color: NextColor.fgWhite60, + ), + ), + if (connectedCount > 0) ...[ + Text( + "•", + style: NextText.bodyXs400.copyWith( + color: NextColor.fgWhite60, + ), + ), + Text( + "$connectedCount online", + style: NextText.bodyXs400.copyWith( + color: Color(0xff74ffb8), + ), + ), + ], + ], + ), + ], + ), + ), + SizedBox(width: NextSpacing.sm), + NextIcon( + "arrow_small", + direction: .right, + color: NextColor.fgWhite60, + ), + ], + ), + ), + ); + } +} + +@Preview(name: 'Connected', group: 'NextInstanceCard') +Widget previewConnected() { + return const NextPreviewWrapper( + child: NextInstanceCard( + locationsCount: 5, + connectedCount: 2, + name: "Warsaw Office", + ), + ); +} + +@Preview(name: 'Disconnected', group: 'NextInstanceCard') +Widget previewDisconnected() { + return const NextPreviewWrapper( + child: NextInstanceCard( + locationsCount: 3, + connectedCount: 0, + name: "Berlin Studio", + ), + ); +} diff --git a/client/lib/open/widgets/next/next_location_card.dart b/client/lib/open/widgets/next/next_location_card.dart index 4bbed441..413315c8 100644 --- a/client/lib/open/widgets/next/next_location_card.dart +++ b/client/lib/open/widgets/next/next_location_card.dart @@ -4,10 +4,13 @@ import 'package:mobile/data/db/database.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/open/widgets/next/icons/next_icon.dart'; import 'package:mobile/open/widgets/next/next_button.dart'; +import 'package:mobile/open/widgets/next/next_preview_wrapper.dart'; import 'package:mobile/theme/next/color.dart'; import 'package:mobile/theme/next/spacing.dart'; import 'package:mobile/theme/next/text.dart'; +import 'next_main_cta.dart'; + class NextLocationCard extends StatelessWidget { final Location location; final bool isConnected; @@ -105,17 +108,23 @@ class NextLocationCard extends StatelessWidget { Row( spacing: NextSpacing.md, children: [ - if (routingMethod != null) - Expanded(child: _InnerInfoCard(routing: routingMethod)), - Expanded(child: _InnerInfoCard(mfaMethod: mfaMethod)), + Expanded( + child: routingMethod != null + ? _InnerInfoCard(routing: routingMethod) + : _InnerInfoCard(mfaMethod: mfaMethod), + ), + Expanded( + child: routingMethod != null + ? _InnerInfoCard(mfaMethod: mfaMethod) + : const SizedBox.shrink(), + ), ], ), const SizedBox(height: NextSpacing.lg), - NextButton( + NextMainCta( text: "Disconnect", + connected: false, onTap: onDisconnectTap, - size: NextButtonSize.big, - style: NextButtonStyle.outlined, ), ], ), @@ -180,21 +189,69 @@ class NextLocationCard extends StatelessWidget { @Preview(name: 'Connected', group: 'NextLocationCard') Widget previewConnected() { - return _PreviewWrapper( + return NextPreviewWrapper( child: NextLocationCard(isConnected: true, location: _mockLocation()), ); } +@Preview(name: 'Connected + MFA TOTP', group: 'NextLocationCard') +Widget previewConnectedMfaTotp() { + return NextPreviewWrapper( + child: NextLocationCard( + isConnected: true, + location: _mockLocation(mfaEnabled: true), + mfaMethod: MfaMethod.totp, + routingMethod: RoutingMethod.all, + ), + ); +} + +@Preview(name: 'Connected + MFA Biometric', group: 'NextLocationCard') +Widget previewConnectedMfaBiometric() { + return NextPreviewWrapper( + child: NextLocationCard( + isConnected: true, + location: _mockLocation(mfaEnabled: true), + mfaMethod: MfaMethod.biometric, + routingMethod: RoutingMethod.all, + ), + ); +} + +@Preview(name: 'Connected + MFA Email', group: 'NextLocationCard') +Widget previewConnectedMfaEmail() { + return NextPreviewWrapper( + child: NextLocationCard( + isConnected: true, + location: _mockLocation(mfaEnabled: true), + mfaMethod: MfaMethod.email, + routingMethod: RoutingMethod.all, + ), + ); +} + +@Preview(name: 'Connected + MFA OpenID', group: 'NextLocationCard') +Widget previewConnectedMfaOpenId() { + return NextPreviewWrapper( + child: NextLocationCard( + isConnected: true, + location: _mockLocation(mfaEnabled: true), + mfaMethod: MfaMethod.openid, + routingMethod: RoutingMethod.all, + ), + ); +} + @Preview(name: 'Not Connected', group: 'NextLocationCard') Widget previewNotConnected() { - return _PreviewWrapper( + return NextPreviewWrapper( child: NextLocationCard(isConnected: false, location: _mockLocation()), ); } @Preview(name: 'Loading', group: 'NextLocationCard') Widget previewLoading() { - return _PreviewWrapper( + return NextPreviewWrapper( child: NextLocationCard( isConnected: false, loading: true, @@ -241,6 +298,7 @@ class _InnerInfoCard extends StatelessWidget { ), Column( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, spacing: 2, children: [ Text( @@ -315,26 +373,6 @@ class _InnerInfoCard extends StatelessWidget { } } -class _PreviewWrapper extends StatelessWidget { - final Widget child; - const _PreviewWrapper({required this.child}); - - @override - Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment(-0.72, -0.69), - end: Alignment(0.72, 0.69), - colors: [Color(0xFF141517), Color(0xFF191A1C)], - ), - ), - padding: const EdgeInsets.all(24.0), - child: Center(child: child), - ); - } -} - Location _mockLocation({bool mfaEnabled = false}) { return Location( id: 1, diff --git a/client/lib/open/widgets/next/next_main_cta.dart b/client/lib/open/widgets/next/next_main_cta.dart new file mode 100644 index 00000000..5abf1da2 --- /dev/null +++ b/client/lib/open/widgets/next/next_main_cta.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widget_previews.dart'; +import 'package:mobile/open/widgets/next/next_preview_wrapper.dart'; +import 'package:mobile/theme/next/color.dart'; +import 'package:mobile/theme/next/spacing.dart'; +import 'package:mobile/theme/next/text.dart'; + +class NextMainCta extends StatelessWidget { + final bool connected; + final String text; + final VoidCallback? onTap; + + const NextMainCta({ + super.key, + required this.text, + this.connected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + const duration = Duration(milliseconds: 200); + const curve = Curves.easeInOut; + + final backgroundColor = connected + ? Colors.transparent + : NextColor.bgWhite100; + final border = connected + ? Border.all(color: NextColor.borderDefault, width: 1) + : null; + final textStyle = (connected ? NextText.bodySm400 : NextText.bodySm600) + .copyWith(color: connected ? NextColor.fgWhite100 : NextColor.fgAction); + + return AnimatedContainer( + duration: duration, + curve: curve, + height: 36, + width: double.infinity, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(100), + border: border, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(100), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: NextSpacing.lg), + child: Center( + child: AnimatedDefaultTextStyle( + duration: duration, + curve: curve, + style: textStyle, + child: Text(text, textAlign: TextAlign.center), + ), + ), + ), + ), + ), + ); + } +} + +@Preview(name: 'Not Connected') +Widget previewNotConnected() { + return NextPreviewWrapper( + child: NextMainCta(text: 'Connect', connected: false, onTap: () {}), + ); +} + +@Preview(name: 'Connected') +Widget previewConnected() { + return NextPreviewWrapper( + child: NextMainCta(text: 'Connected', connected: true, onTap: () {}), + ); +} diff --git a/client/lib/open/widgets/next/next_preview_wrapper.dart b/client/lib/open/widgets/next/next_preview_wrapper.dart new file mode 100644 index 00000000..903383bf --- /dev/null +++ b/client/lib/open/widgets/next/next_preview_wrapper.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:mobile/theme/next/color.dart'; + +class NextPreviewWrapper extends StatelessWidget { + final Widget child; + final double? width; + final EdgeInsetsGeometry padding; + + const NextPreviewWrapper({ + super.key, + required this.child, + this.width, + this.padding = const EdgeInsets.all(24.0), + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration(gradient: NextColor.previewGradient), + padding: padding, + child: Center( + child: width != null ? SizedBox(width: width, child: child) : child, + ), + ); + } +} diff --git a/client/lib/open/widgets/next/next_text_form_field.dart b/client/lib/open/widgets/next/next_text_form_field.dart index 34de833d..3259b7d3 100644 --- a/client/lib/open/widgets/next/next_text_form_field.dart +++ b/client/lib/open/widgets/next/next_text_form_field.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widget_previews.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:mobile/open/widgets/next/next_preview_wrapper.dart'; import 'package:mobile/theme/next/color.dart'; import 'package:mobile/theme/next/spacing.dart'; import 'package:mobile/theme/next/text.dart'; @@ -285,24 +286,11 @@ class _NextTextFormFieldContent extends HookWidget { } } -Widget _previewWrapper(Widget child) { - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment(-0.72, -0.69), - end: Alignment(0.72, 0.69), - colors: [Color(0xFF5B83FF), Color(0xFF0036DB)], - ), - ), - padding: const EdgeInsets.all(24.0), - child: Center(child: SizedBox(width: 320, child: child)), - ); -} - @Preview(name: 'Default', group: 'Primary') Widget previewPrimaryDefault() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.primary, hintText: 'Enter text...', ), @@ -311,8 +299,9 @@ Widget previewPrimaryDefault() { @Preview(name: 'With Label & Required', group: 'Primary') Widget previewPrimaryWithLabel() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.primary, label: 'Email address', required: true, @@ -323,8 +312,9 @@ Widget previewPrimaryWithLabel() { @Preview(name: 'With Text', group: 'Primary') Widget previewPrimaryWithText() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.primary, label: 'Username', controller: TextEditingController(text: 'antigravity_user'), @@ -334,8 +324,9 @@ Widget previewPrimaryWithText() { @Preview(name: 'Disabled', group: 'Primary') Widget previewPrimaryDisabled() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.primary, label: 'Role', controller: TextEditingController(text: 'Administrator'), @@ -346,8 +337,9 @@ Widget previewPrimaryDisabled() { @Preview(name: 'With Error', group: 'Primary') Widget previewPrimaryWithError() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.primary, label: 'Password', required: true, @@ -360,8 +352,9 @@ Widget previewPrimaryWithError() { @Preview(name: 'Default', group: 'Big') Widget previewBigDefault() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.big, hintText: 'Search location...', ), @@ -370,8 +363,9 @@ Widget previewBigDefault() { @Preview(name: 'With Label & Required', group: 'Big') Widget previewBigWithLabel() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.big, label: 'Server Name', required: true, @@ -382,8 +376,9 @@ Widget previewBigWithLabel() { @Preview(name: 'Disabled', group: 'Big') Widget previewBigDisabled() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.big, label: 'Server Name', controller: TextEditingController(text: 'Production Server'), @@ -394,8 +389,9 @@ Widget previewBigDisabled() { @Preview(name: 'With Error', group: 'Big') Widget previewBigWithError() { - return _previewWrapper( - NextTextFormField( + return NextPreviewWrapper( + width: 320, + child: NextTextFormField( size: NextTextFormFieldSize.big, label: 'Port', required: true, diff --git a/client/lib/theme/next/color.dart b/client/lib/theme/next/color.dart index 016a6d7a..6a623c3b 100644 --- a/client/lib/theme/next/color.dart +++ b/client/lib/theme/next/color.dart @@ -105,6 +105,10 @@ class _Primitive { static const Color saturatedBlue100 = Color(0xffedf1fc); static const Color saturatedBlue50 = Color(0xfff9fafe); static const Color saturatedBlue500Transparent = Color(0x14325cdb); + + // Gradient Colors + static const Color gradientBlue1 = Color(0xff4F79FA); + static const Color gradientBlue2 = Color(0xff0A3EDF); } class NextColor { @@ -174,4 +178,12 @@ class NextColor { static const Color fgMuted = _Primitive.darkNeutral600; static const Color fgDisabled = _Primitive.darkNeutral500; static const Color fgSuccess = _Primitive.saturatedSuccess; + + // Gradients + static const LinearGradient previewGradient = LinearGradient( + begin: Alignment(-0.7, -1.0), + end: Alignment(0.7, 1.0), + colors: [_Primitive.gradientBlue1, _Primitive.gradientBlue2], + ); + static const LinearGradient gradientPrimary = previewGradient; } diff --git a/client/lib/theme/next/text.dart b/client/lib/theme/next/text.dart index 347a95de..d9b96aa4 100644 --- a/client/lib/theme/next/text.dart +++ b/client/lib/theme/next/text.dart @@ -125,7 +125,7 @@ class NextText { static const TextStyle h5 = TextStyle( fontFamily: _defaultFontFamily, fontSize: 18, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w500, height: 28 / 18, ); @@ -133,7 +133,7 @@ class NextText { static const TextStyle inputTitle = TextStyle( fontFamily: _defaultFontFamily, fontSize: 12, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w300, height: 16 / 12, ); static const TextStyle inputPrimary = TextStyle( @@ -159,7 +159,7 @@ class NextText { static const TextStyle menuTitle = TextStyle( fontFamily: _defaultFontFamily, fontSize: 12, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w300, height: 2, ); static const TextStyle menuText = TextStyle( @@ -173,16 +173,16 @@ class NextText { static const TextStyle buttonLabelBig = TextStyle( fontFamily: _defaultFontFamily, fontSize: 14, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w500, ); static const TextStyle buttonLabelPrimary = TextStyle( fontFamily: _defaultFontFamily, fontSize: 14, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w500, ); static const TextStyle buttonLabelSecondary = TextStyle( fontFamily: _defaultFontFamily, fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w400, ); } From e8516e9f3555eb0750ae4d5cd0c7e6ca3979e8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 13 Aug 2026 11:10:34 +0200 Subject: [PATCH 52/65] new splash screen --- client/lib/open/riverpod/router/router.dart | 2 +- client/lib/open/riverpod/router/router.g.dart | 2 +- .../biometry/biometry_finish_screen.dart | 2 +- .../biometry_setup_failed_screen.dart | 2 +- .../biometry/biometry_setup_screen.dart | 4 +- .../screens/register_from_qr_screen.dart | 2 +- ...screen.dart => instances_list_screen.dart} | 14 +----- .../screens/instance/instance_screen.dart | 6 +-- .../lib/open/screens/process_qr_screen.dart | 2 +- client/lib/open/screens/splash.dart | 48 +++++++++++++++++++ .../open/widgets/navigation/dg_drawer.dart | 5 +- client/lib/router/routes.dart | 23 +++++++-- client/lib/router/routes.g.dart | 40 +++++++++++++--- 13 files changed, 117 insertions(+), 35 deletions(-) rename client/lib/open/screens/home/{home_screen.dart => instances_list_screen.dart} (90%) create mode 100644 client/lib/open/screens/splash.dart diff --git a/client/lib/open/riverpod/router/router.dart b/client/lib/open/riverpod/router/router.dart index fc449238..f6f23dbb 100644 --- a/client/lib/open/riverpod/router/router.dart +++ b/client/lib/open/riverpod/router/router.dart @@ -10,7 +10,7 @@ part 'router.g.dart'; @riverpod GoRouter router(Ref ref) { return GoRouter( - initialLocation: HomeScreenRoute().location, + initialLocation: const AppSplashRoute().location, routes: $appRoutes, observers: [TalkerRouteObserver(talker)], ); diff --git a/client/lib/open/riverpod/router/router.g.dart b/client/lib/open/riverpod/router/router.g.dart index 72aefd72..d4179c69 100644 --- a/client/lib/open/riverpod/router/router.g.dart +++ b/client/lib/open/riverpod/router/router.g.dart @@ -48,4 +48,4 @@ final class RouterProvider } } -String _$routerHash() => r'f5f080022520cf0ad41a6c2dd115809788b80104'; +String _$routerHash() => r'0bbec83cd17d0d18c687ba87021d1028671f5416'; diff --git a/client/lib/open/screens/add_instance/screens/biometry/biometry_finish_screen.dart b/client/lib/open/screens/add_instance/screens/biometry/biometry_finish_screen.dart index 20c3ab6d..cfe53e6a 100644 --- a/client/lib/open/screens/add_instance/screens/biometry/biometry_finish_screen.dart +++ b/client/lib/open/screens/add_instance/screens/biometry/biometry_finish_screen.dart @@ -44,7 +44,7 @@ class BiometryFinishScreen extends StatelessWidget { size: DgButtonSize.big, variant: DgButtonVariant.primary, onTap: () { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); }, ), ], diff --git a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_failed_screen.dart b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_failed_screen.dart index 85719e2c..d5963e26 100644 --- a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_failed_screen.dart +++ b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_failed_screen.dart @@ -51,7 +51,7 @@ class BiometrySetupFailedScreen extends StatelessWidget { size: DgButtonSize.big, variant: DgButtonVariant.secondary, onTap: () { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); }, ), ), diff --git a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart index 8c44d95e..ec736c60 100644 --- a/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart +++ b/client/lib/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart @@ -123,7 +123,7 @@ class _ScreenContent extends HookConsumerWidget { loading: () => LoadingView(), error: (err, _) { talker.error("Failed to get screen data", err); - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); return const SizedBox(); }, data: (instance) => DgSingleChildScrollView( @@ -203,7 +203,7 @@ class _ScreenContent extends HookConsumerWidget { variant: DgButtonVariant.secondary, disabled: isLoading.value, onTap: () { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); }, ), ), diff --git a/client/lib/open/screens/add_instance/screens/register_from_qr_screen.dart b/client/lib/open/screens/add_instance/screens/register_from_qr_screen.dart index 9ebdc3f4..7c2bd4ff 100644 --- a/client/lib/open/screens/add_instance/screens/register_from_qr_screen.dart +++ b/client/lib/open/screens/add_instance/screens/register_from_qr_screen.dart @@ -41,7 +41,7 @@ class RegisterFromQrScreen extends HookConsumerWidget { SnackbarService.showError("Instance is already registered!"); WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); } }); return; diff --git a/client/lib/open/screens/home/home_screen.dart b/client/lib/open/screens/home/instances_list_screen.dart similarity index 90% rename from client/lib/open/screens/home/home_screen.dart rename to client/lib/open/screens/home/instances_list_screen.dart index b419375a..109de0f0 100644 --- a/client/lib/open/screens/home/home_screen.dart +++ b/client/lib/open/screens/home/instances_list_screen.dart @@ -13,8 +13,8 @@ import 'package:mobile/theme/color.dart'; import 'package:mobile/theme/spacing.dart'; import 'package:mobile/theme/text.dart'; -class HomeScreen extends StatelessWidget { - const HomeScreen({super.key}); +class InstancesListScreen extends StatelessWidget { + const InstancesListScreen({super.key}); @override Widget build(BuildContext context) { @@ -69,19 +69,9 @@ class _InstancesList extends HookConsumerWidget { return asyncInstances.when( data: (instances) { if (instances.isEmpty) { - WidgetsBinding.instance.addPostFrameCallback((_) { - AddInstanceScreenRoute().go(context); - }); return Center(child: Text("No instances found", style: DgText.body1)); } - if (instances.length == 1) { - WidgetsBinding.instance.addPostFrameCallback((_) { - final instance = instances[0]; - InstanceScreenRoute(id: instance.id.toString()).go(context); - }); - } - return CustomScrollView( slivers: [ SliverToBoxAdapter(child: SizedBox(height: DgSpacing.s)), diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart index 05e15b73..507d416f 100644 --- a/client/lib/open/screens/instance/instance_screen.dart +++ b/client/lib/open/screens/instance/instance_screen.dart @@ -142,7 +142,7 @@ class InstanceScreen extends HookConsumerWidget { talker.debug("Instance $id not found id DB, redirecting."); Future.microtask(() { if (context.mounted) { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); } }); return const SizedBox(); @@ -158,7 +158,7 @@ class InstanceScreen extends HookConsumerWidget { loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) { talker.error("Instance route screen data returned error", err); - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); return const SizedBox(); }, ), @@ -243,7 +243,7 @@ class _ScreenContent extends HookConsumerWidget { direction: DgIconDirection.left, ), onTap: () { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); }, ), SizedBox( diff --git a/client/lib/open/screens/process_qr_screen.dart b/client/lib/open/screens/process_qr_screen.dart index 1fc4da1b..7e3f7688 100644 --- a/client/lib/open/screens/process_qr_screen.dart +++ b/client/lib/open/screens/process_qr_screen.dart @@ -61,7 +61,7 @@ class ProcessQrScreen extends HookConsumerWidget { } else { await WidgetsBinding.instance.endOfFrame; if (context.mounted) { - HomeScreenRoute().go(context); + InstancesListScreenRoute().go(context); } } break; diff --git a/client/lib/open/screens/splash.dart b/client/lib/open/screens/splash.dart new file mode 100644 index 00000000..9e33a8cd --- /dev/null +++ b/client/lib/open/screens/splash.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mobile/data/db/database.dart'; +import 'package:mobile/router/routes.dart'; +import 'package:mobile/theme/next/color.dart'; + +class AppSplash extends HookConsumerWidget { + const AppSplash({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final db = ref.watch(databaseProvider); + final instancesAsync = useStream( + useMemoized(() => db.select(db.defguardInstances).watch(), [db]), + ); + + final timerDone = useState(false); + + useEffect(() { + final timer = Timer(const Duration(milliseconds: 1500), () { + timerDone.value = true; + }); + return timer.cancel; + }, []); + + useEffect(() { + final instances = instancesAsync.data; + if (instances != null && timerDone.value) { + if (instances.isEmpty) { + const AddInstanceScreenRoute().go(context); + } else if (instances.length == 1) { + InstanceScreenRoute(id: instances[0].id.toString()).go(context); + } else { + const InstancesListScreenRoute().go(context); + } + } + return null; + }, [instancesAsync.data, timerDone.value]); + + return Container( + decoration: const BoxDecoration(gradient: NextColor.gradientPrimary), + child: Center(child: Image.asset("assets/splash/logo.png")), + ); + } +} diff --git a/client/lib/open/widgets/navigation/dg_drawer.dart b/client/lib/open/widgets/navigation/dg_drawer.dart index c7cd98ee..b6a1a3e5 100644 --- a/client/lib/open/widgets/navigation/dg_drawer.dart +++ b/client/lib/open/widgets/navigation/dg_drawer.dart @@ -64,7 +64,10 @@ class DgDrawer extends HookConsumerWidget { return [ if (instancesAsync.value != null && instancesAsync.value!.length > 1) - _DrawerItemData(label: "Instances", route: HomeScreenRoute()), + _DrawerItemData( + label: "Instances", + route: InstancesListScreenRoute(), + ), if (instancesAsync.value != null && instancesAsync.value!.length == 1) _DrawerItemData( diff --git a/client/lib/router/routes.dart b/client/lib/router/routes.dart index ca7ddcc4..50a33098 100644 --- a/client/lib/router/routes.dart +++ b/client/lib/router/routes.dart @@ -8,11 +8,12 @@ import 'package:mobile/open/screens/add_instance/screens/biometry/biometry_finis import 'package:mobile/open/screens/add_instance/screens/biometry/biometry_setup_failed_screen.dart'; import 'package:mobile/open/screens/add_instance/screens/biometry/biometry_setup_screen.dart'; import 'package:mobile/open/screens/add_instance/screens/name_device_screen.dart'; -import 'package:mobile/open/screens/home/home_screen.dart'; +import 'package:mobile/open/screens/home/instances_list_screen.dart'; import 'package:mobile/open/screens/instance/instance_screen.dart'; import 'package:mobile/open/screens/mfa/mfa_code_screen.dart'; import 'package:mobile/open/screens/process_qr_screen.dart'; import 'package:mobile/open/screens/scan_qr_screen.dart'; +import 'package:mobile/open/screens/splash.dart'; import 'package:talker_flutter/talker_flutter.dart'; import '../logging.dart'; @@ -32,14 +33,26 @@ class ProcessQrScreenRoute extends GoRouteData with $ProcessQrScreenRoute { } } -@TypedGoRoute(path: '/') +@TypedGoRoute(path: '/') @immutable -class HomeScreenRoute extends GoRouteData with $HomeScreenRoute { - const HomeScreenRoute(); +class AppSplashRoute extends GoRouteData with $AppSplashRoute { + const AppSplashRoute(); @override Widget build(BuildContext context, GoRouterState state) { - return const HomeScreen(); + return const AppSplash(); + } +} + +@TypedGoRoute(path: '/home') +@immutable +class InstancesListScreenRoute extends GoRouteData + with $InstancesListScreenRoute { + const InstancesListScreenRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const InstancesListScreen(); } } diff --git a/client/lib/router/routes.g.dart b/client/lib/router/routes.g.dart index 1b3fac66..0d1ae1d9 100644 --- a/client/lib/router/routes.g.dart +++ b/client/lib/router/routes.g.dart @@ -8,7 +8,8 @@ part of 'routes.dart'; List get $appRoutes => [ $processQrScreenRoute, - $homeScreenRoute, + $appSplashRoute, + $instancesListScreenRoute, $qRScreenRoute, $instanceScreenRoute, $nameDeviceScreenRoute, @@ -54,15 +55,15 @@ mixin $ProcessQrScreenRoute on GoRouteData { context.replace(location, extra: _self.$extra); } -RouteBase get $homeScreenRoute => GoRouteData.$route( +RouteBase get $appSplashRoute => GoRouteData.$route( path: '/', hasOverriddenOnExit: false, - factory: $HomeScreenRoute._fromState, + factory: $AppSplashRoute._fromState, ); -mixin $HomeScreenRoute on GoRouteData { - static HomeScreenRoute _fromState(GoRouterState state) => - const HomeScreenRoute(); +mixin $AppSplashRoute on GoRouteData { + static AppSplashRoute _fromState(GoRouterState state) => + const AppSplashRoute(); @override String get location => GoRouteData.$location('/'); @@ -81,6 +82,33 @@ mixin $HomeScreenRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +RouteBase get $instancesListScreenRoute => GoRouteData.$route( + path: '/home', + hasOverriddenOnExit: false, + factory: $InstancesListScreenRoute._fromState, +); + +mixin $InstancesListScreenRoute on GoRouteData { + static InstancesListScreenRoute _fromState(GoRouterState state) => + const InstancesListScreenRoute(); + + @override + String get location => GoRouteData.$location('/home'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + RouteBase get $qRScreenRoute => GoRouteData.$route( path: '/qr', hasOverriddenOnExit: false, From 6c1fb43720c325ad3a50a0a9eab4a26a5c20356e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 13 Aug 2026 11:21:42 +0200 Subject: [PATCH 53/65] Update splash.dart --- client/lib/open/screens/splash.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/open/screens/splash.dart b/client/lib/open/screens/splash.dart index 9e33a8cd..404e7541 100644 --- a/client/lib/open/screens/splash.dart +++ b/client/lib/open/screens/splash.dart @@ -20,7 +20,7 @@ class AppSplash extends HookConsumerWidget { final timerDone = useState(false); useEffect(() { - final timer = Timer(const Duration(milliseconds: 1500), () { + final timer = Timer(const Duration(milliseconds: 300), () { timerDone.value = true; }); return timer.cancel; From e3e98e4181e10b92b626287424c19d304224a81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 13 Aug 2026 11:34:22 +0200 Subject: [PATCH 54/65] bumped splash style --- client/analysis_options.yaml | 7 ++ .../drawable-hdpi-v31/android12branding.png | Bin 9763 -> 9735 bytes .../src/main/res/drawable-hdpi/branding.png | Bin 9763 -> 9735 bytes .../drawable-mdpi-v31/android12branding.png | Bin 6403 -> 6375 bytes .../src/main/res/drawable-mdpi/branding.png | Bin 6403 -> 6375 bytes .../android12branding.png | Bin 9763 -> 9735 bytes .../android12branding.png | Bin 6403 -> 6375 bytes .../android12branding.png | Bin 11153 -> 11125 bytes .../android12branding.png | Bin 16275 -> 16247 bytes .../android12branding.png | Bin 19859 -> 19831 bytes .../src/main/res/drawable-v21/background.png | Bin 69 -> 69 bytes .../drawable-xhdpi-v31/android12branding.png | Bin 11153 -> 11125 bytes .../src/main/res/drawable-xhdpi/branding.png | Bin 11153 -> 11125 bytes .../drawable-xxhdpi-v31/android12branding.png | Bin 16275 -> 16247 bytes .../src/main/res/drawable-xxhdpi/branding.png | Bin 16275 -> 16247 bytes .../android12branding.png | Bin 19859 -> 19831 bytes .../main/res/drawable-xxxhdpi/branding.png | Bin 19859 -> 19831 bytes .../app/src/main/res/drawable/background.png | Bin 69 -> 69 bytes .../app/src/main/res/drawable/gradient_bg.xml | 8 ++ .../src/main/res/values-night-v31/styles.xml | 4 +- .../app/src/main/res/values-v31/styles.xml | 4 +- .../app/src/main/res/values/colors.xml | 2 +- client/flutter_launcher_icons.yaml | 4 +- .../BrandingImage.imageset/BrandingImage.png | Bin 6403 -> 6375 bytes .../BrandingImage@2x.png | Bin 11153 -> 11125 bytes .../BrandingImage@3x.png | Bin 16275 -> 16247 bytes .../LaunchBackground.imageset/background.png | Bin 69 -> 69 bytes client/ios/Runner/Info.plist | 106 +++++++++--------- client/lib/open/screens/splash.dart | 31 +++-- client/pubspec.lock | 24 ++-- client/pubspec.yaml | 5 +- 31 files changed, 113 insertions(+), 82 deletions(-) create mode 100644 client/android/app/src/main/res/drawable/gradient_bg.xml diff --git a/client/analysis_options.yaml b/client/analysis_options.yaml index c3a2ff4f..a7129a7a 100644 --- a/client/analysis_options.yaml +++ b/client/analysis_options.yaml @@ -29,6 +29,13 @@ linter: analyzer: exclude: - "**/*.g.dart" + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** plugins: - custom_lint diff --git a/client/android/app/src/main/res/drawable-hdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-hdpi-v31/android12branding.png index 01587b78f7c10a0fcb8b245e316811f2132468b6..34c1759b665dd335de74788c25678cf7aa4fb5b5 100644 GIT binary patch delta 395 zcmV;60d)SOOovR6B!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XBwa~FK~#90?VWj?B(uE%W+WaR#Jm6i delta 409 zcmV;K0cQS(OruPYB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^yxj Dx<$o8 diff --git a/client/android/app/src/main/res/drawable-hdpi/branding.png b/client/android/app/src/main/res/drawable-hdpi/branding.png index 01587b78f7c10a0fcb8b245e316811f2132468b6..34c1759b665dd335de74788c25678cf7aa4fb5b5 100644 GIT binary patch delta 395 zcmV;60d)SOOovR6B!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XBwa~FK~#90?VWj?B(uE%W+WaR#Jm6i delta 409 zcmV;K0cQS(OruPYB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^yxj Dx<$o8 diff --git a/client/android/app/src/main/res/drawable-mdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-mdpi-v31/android12branding.png index 8423ed0fe5f254b44898593a78e3c61044043369..7580aa1c9c7ce06de2cde380c4615cefb8f1fd18 100644 GIT binary patch delta 395 zcmV;60d)R@GUqXnB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05X7d=TtK~#90?VSspB(uE%Mi(dg!~XyP delta 409 zcmV;K0cQT^F@rLYB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^xm0 D%rwOy diff --git a/client/android/app/src/main/res/drawable-mdpi/branding.png b/client/android/app/src/main/res/drawable-mdpi/branding.png index 8423ed0fe5f254b44898593a78e3c61044043369..7580aa1c9c7ce06de2cde380c4615cefb8f1fd18 100644 GIT binary patch delta 395 zcmV;60d)R@GUqXnB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05X7d=TtK~#90?VSspB(uE%Mi(dg!~XyP delta 409 zcmV;K0cQT^F@rLYB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^xm0 D%rwOy diff --git a/client/android/app/src/main/res/drawable-night-hdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-night-hdpi-v31/android12branding.png index 01587b78f7c10a0fcb8b245e316811f2132468b6..34c1759b665dd335de74788c25678cf7aa4fb5b5 100644 GIT binary patch delta 395 zcmV;60d)SOOovR6B!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XBwa~FK~#90?VWj?B(uE%W+WaR#Jm6i delta 409 zcmV;K0cQS(OruPYB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^yxj Dx<$o8 diff --git a/client/android/app/src/main/res/drawable-night-mdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-night-mdpi-v31/android12branding.png index 8423ed0fe5f254b44898593a78e3c61044043369..7580aa1c9c7ce06de2cde380c4615cefb8f1fd18 100644 GIT binary patch delta 395 zcmV;60d)R@GUqXnB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05X7d=TtK~#90?VSspB(uE%Mi(dg!~XyP delta 409 zcmV;K0cQT^F@rLYB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^xm0 D%rwOy diff --git a/client/android/app/src/main/res/drawable-night-xhdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-night-xhdpi-v31/android12branding.png index 6a8b0553f00e310e31800514d350dc41d33982c8..a827e3d20fa5d28703245195985440d390d7a021 100644 GIT binary patch delta 395 zcmV;60d)S6SM^qqB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XDa%PjK~#90?cE8yb+f$z)G5`1##8_R delta 409 zcmV;K0cQU7R*_ebB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^$o> DZ@0!K diff --git a/client/android/app/src/main/res/drawable-night-xxhdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-night-xxhdpi-v31/android12branding.png index e52dc5b7bd88e0732a31fc1ef137677ae737aa59..a58927a7e33f561510dbcd851b0e06450e815c30 100644 GIT binary patch delta 395 zcmV;60d)S8fA@ZnB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XJw!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^$vC D^flhZ2mlsRn<&`Vp#5a57Oe)G$qojXf`E131w)yoz0Pkjpx9lwY zSL;bcuotJJemo#Jhou{X{*dn?AGNBh1kW)O|9g<&J@T~?e?`7I;y=j8mGT0ag9w{j zxVD9(R@@;|z;vg*76AfG*1MOMaRgHmWs6Ee=bf>jXwE4`MYHA|H^(;n0;XKl6N>co zt&3gP)G{z5v?fu17ix&r)wQh;{r~SZX`Gk`!DNZuU()o)Q6}!Nxsj&7AE)W&6SALq z)gAA9k2v|0?8dm8en{>XFJ6zk+C5(0=J2O}HLijzGK~h`pK)M?LvPu4b>s|Lc>0mb rO_ttp{{hDu5QjhG zBchQAT3A>FiB?8pgrJQ@M5CYuoeVG5yVz0ToOnSGt2CJ``evicNT2nw35*qypYKnS|}KBEi5j%>~A!G;lzO-TSU>-nYoED zHSOzxIxF6JtXj3t{cEkUxUN85v+$^*YKHKn@bsFYTD&XVsiYz?;d9}BO)e;WZ|Ttz zzgnCu@sGt?I5Y;fc41etilbGmq_m8%yc~BXoma~Dv0mbcXXoc6_K0_*6Cy|e7gLNg zCOsKsL}ou8d<@8ciW>ReV*V^S$yH=lSw~})BqYO}MzOXwf~m+=8wr zHKIj|QoXn>k%0FzXj_DiTWDD>^{l+l=_7DouDK6as3}w2Bx=+$EW`}tib>Tp00i_>zopr0JZlEC;$Ke delta 30 kcmZ>DouDK6@MR?f1Cs$`?S~VOR2YE3)78&qol`;+0G_o90{{R3 diff --git a/client/android/app/src/main/res/drawable-xhdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-xhdpi-v31/android12branding.png index 6a8b0553f00e310e31800514d350dc41d33982c8..a827e3d20fa5d28703245195985440d390d7a021 100644 GIT binary patch delta 395 zcmV;60d)S6SM^qqB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XDa%PjK~#90?cE8yb+f$z)G5`1##8_R delta 409 zcmV;K0cQU7R*_ebB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^$o> DZ@0!K diff --git a/client/android/app/src/main/res/drawable-xhdpi/branding.png b/client/android/app/src/main/res/drawable-xhdpi/branding.png index 6a8b0553f00e310e31800514d350dc41d33982c8..a827e3d20fa5d28703245195985440d390d7a021 100644 GIT binary patch delta 395 zcmV;60d)S6SM^qqB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XDa%PjK~#90?cE8yb+f$z)G5`1##8_R delta 409 zcmV;K0cQU7R*_ebB!7WvLqkw=Qb$4{Nkv08F*!CiEix`K001bFb&w!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^$o> DZ@0!K diff --git a/client/android/app/src/main/res/drawable-xxhdpi-v31/android12branding.png b/client/android/app/src/main/res/drawable-xxhdpi-v31/android12branding.png index e52dc5b7bd88e0732a31fc1ef137677ae737aa59..a58927a7e33f561510dbcd851b0e06450e815c30 100644 GIT binary patch delta 395 zcmV;60d)S8fA@ZnB!6XTLqkw$V`BgSc-ocFyK7Sc7>4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XJw!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^$vC D^4oRNz{r~BACIU!$GQ3D`|zG zi$jW5p*o}ythywpxd^#&PKp#)!L^_ux_BG(FVIzR76e5R#93EG7cECah7_Sg-|69n z_v7OsQ)x$?WLJSscMz8sOX1~}E8)a9d*n^+5pdW{0=zEc;jM zNkp(0r=xy6AUKDm8-xCk?;;E;u%pLx|C z?|Y9p`IGF%xSM`R?iMdzkGtAEUf$;Lr+ziAf-EwP2H&4?V1+|(*>`p13|V;kk;+Y$ p-f;f`#~S3W{RLjTXW|-<_J05XJw!8e-g&HAwa@))t+BYSKwY!&sG@3y@TBncnxR^}E8MB1A~E4};eJgnD12|}(GtH} zoGkH=#acKt2DWx#SF(zuRjj16jIg{McP5=z%J;Ee;)rMG=Op%sccT*`NB|d8j58)Z z8DvCeKOTGx$bX6&`QBpwEIG+lWL8;6W0fQ+4EjaQOZB+Kgy?Y@Y1Xa&|Lv=u81j{> zI|9)7VwxYlVBdklwrPGJn`Yqz>Yw2%TglHI!SJW(+ZD}y2+mEoxUOi?J-FP0t|v93 zMT%0rxGj-@_cLf)gpONiSuXXgywB+)gg4}?Zeafa`VHi~bLAIayJzAbB$9cvk^$vC D^flhZ2mlsRn<&`Vp#5a57Oe)G$qojXf`E131w)yoz0Pkjpx9lwY zSL;bcuotJJemo#Jhou{X{*dn?AGNBh1kW)O|9g<&J@T~?e?`7I;y=j8mGT0ag9w{j zxVD9(R@@;|z;vg*76AfG*1MOMaRgHmWs6Ee=bf>jXwE4`MYHA|H^(;n0;XKl6N>co zt&3gP)G{z5v?fu17ix&r)wQh;{r~SZX`Gk`!DNZuU()o)Q6}!Nxsj&7AE)W&6SALq z)gAA9k2v|0?8dm8en{>XFJ6zk+C5(0=J2O}HLijzGK~h`pK)M?LvPu4b>s|Lc>0mb rO_ttp{{hDu5QjhG zBchQAT3A>FiB?8pgrJQ@M5CYuoeVG5yVz0ToOnSGt2CJ``evicNT2nw35*qypYKnS|}KBEi5j%>~A!G;lzO-TSU>-nYoED zHSOzxIxF6JtXj3t{cEkUxUN85v+$^*YKHKn@bsFYTD&XVsiYz?;d9}BO)e;WZ|Ttz zzgnCu@sGt?I5Y;fc41etilbGmq_m8%yc~BXoma~Dv0mbcXXoc6_K0_*6Cy|e7gLNg zCOsKsL}ou8d<@8ciW>ReV*V^S$yH=lSw~})BqYO}MzOXwf~m+=8wr zHKIj|QoXn>k%0FzXj_DiTWDD>^{l+l=_7flhZ2mlsRn<&`Vp#5a57Oe)G$qojXf`E131w)yoz0Pkjpx9lwY zSL;bcuotJJemo#Jhou{X{*dn?AGNBh1kW)O|9g<&J@T~?e?`7I;y=j8mGT0ag9w{j zxVD9(R@@;|z;vg*76AfG*1MOMaRgHmWs6Ee=bf>jXwE4`MYHA|H^(;n0;XKl6N>co zt&3gP)G{z5v?fu17ix&r)wQh;{r~SZX`Gk`!DNZuU()o)Q6}!Nxsj&7AE)W&6SALq z)gAA9k2v|0?8dm8en{>XFJ6zk+C5(0=J2O}HLijzGK~h`pK)M?LvPu4b>s|Lc>0mb rO_ttp{{hDu5QjhG zBchQAT3A>FiB?8pgrJQ@M5CYuoeVG5yVz0ToOnSGt2CJ``evicNT2nw35*qypYKnS|}KBEi5j%>~A!G;lzO-TSU>-nYoED zHSOzxIxF6JtXj3t{cEkUxUN85v+$^*YKHKn@bsFYTD&XVsiYz?;d9}BO)e;WZ|Ttz zzgnCu@sGt?I5Y;fc41etilbGmq_m8%yc~BXoma~Dv0mbcXXoc6_K0_*6Cy|e7gLNg zCOsKsL}ou8d<@8ciW>ReV*V^S$yH=lSw~})BqYO}MzOXwf~m+=8wr zHKIj|QoXn>k%0FzXj_DiTWDD>^{l+l=_7DouDK6as3}w2Bx=+$EW`}tib>Tp00i_>zopr0JZlEC;$Ke delta 30 kcmZ>DouDK6@MR?f1Cs$`?S~VOR2YE3)78&qol`;+0G_o90{{R3 diff --git a/client/android/app/src/main/res/drawable/gradient_bg.xml b/client/android/app/src/main/res/drawable/gradient_bg.xml new file mode 100644 index 00000000..c33d47e4 --- /dev/null +++ b/client/android/app/src/main/res/drawable/gradient_bg.xml @@ -0,0 +1,8 @@ + + + + diff --git a/client/android/app/src/main/res/values-night-v31/styles.xml b/client/android/app/src/main/res/values-night-v31/styles.xml index 0ad85387..c1a37b58 100644 --- a/client/android/app/src/main/res/values-night-v31/styles.xml +++ b/client/android/app/src/main/res/values-night-v31/styles.xml @@ -6,10 +6,10 @@ false false shortEdges - #0C8CE0 + #4F79FA @drawable/android12branding @drawable/android12splash - #0c8ce0 + #4F79FA

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 z