add fingerprinting configuration

This commit is contained in:
Fabian Freund
2025-10-11 14:52:09 +02:00
parent 723f22e04c
commit 36a67ee09a
24 changed files with 642 additions and 10 deletions
+1
View File
@@ -43,6 +43,7 @@ import 'package:weblibre/features/settings/presentation/screens/addon_collection
import 'package:weblibre/features/settings/presentation/screens/bang_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/developer_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/doh_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/fingerprint_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/general_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/settings.dart';
import 'package:weblibre/features/settings/presentation/screens/web_engine_hardening.dart';
+27
View File
@@ -111,6 +111,11 @@ RouteBase get $settingsRoute => GoRouteData.$route(
name: 'DohSettingsRoute',
factory: $DohSettingsRoute._fromState,
),
GoRouteData.$route(
path: 'fingerprint',
name: 'FingerprintSettingsRoute',
factory: $FingerprintSettingsRoute._fromState,
),
],
),
GoRouteData.$route(
@@ -279,6 +284,28 @@ mixin $DohSettingsRoute on GoRouteData {
void replace(BuildContext context) => context.replace(location);
}
mixin $FingerprintSettingsRoute on GoRouteData {
static FingerprintSettingsRoute _fromState(GoRouterState state) =>
FingerprintSettingsRoute();
@override
String get location =>
GoRouteData.$location('/settings/web_engine/fingerprint');
@override
void go(BuildContext context) => context.go(location);
@override
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
@override
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
@override
void replace(BuildContext context) => context.replace(location);
}
mixin $DeveloperSettingsRoute on GoRouteData {
static DeveloperSettingsRoute _fromState(GoRouterState state) =>
DeveloperSettingsRoute();
+12
View File
@@ -43,6 +43,10 @@ part of 'routes.dart';
],
),
TypedGoRoute<DohSettingsRoute>(name: 'DohSettingsRoute', path: 'doh'),
TypedGoRoute<FingerprintSettingsRoute>(
name: 'FingerprintSettingsRoute',
path: 'fingerprint',
),
],
),
TypedGoRoute<DeveloperSettingsRoute>(
@@ -85,6 +89,14 @@ class DohSettingsRoute extends GoRouteData with $DohSettingsRoute {
}
}
class FingerprintSettingsRoute extends GoRouteData
with $FingerprintSettingsRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
return const FingerprintSettingsScreen();
}
}
class WebEngineSettingsRoute extends GoRouteData with $WebEngineSettingsRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
@@ -128,6 +128,12 @@ class EngineSettingsReplicationService
if (previous.value?.dohSettings != settings.dohSettings) {
await _service.dohSettings(settings.dohSettings);
}
if (previous.value?.fingerprintingProtectionOverrides !=
settings.fingerprintingProtectionOverrides) {
await _service.fingerprintingProtectionOverrides(
settings.fingerprintingProtectionOverrides,
);
}
} else {
await _service.setDefaultSettings(settings);
initialSettingsSent = true;
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
}
String _$engineSettingsReplicationServiceHash() =>
r'0bbc80fe0cff79f9e419ffa8c486d232f9eac2ee';
r'3c96e6e45537399582fdf6e8b1fafc44c613b89d';
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
void build();
@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart';
import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart';
import 'package:weblibre/features/user/domain/providers.dart';
import 'package:weblibre/features/user/domain/services/fingerprinting.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
class FingerprintSettingsScreen extends HookConsumerWidget {
const FingerprintSettingsScreen();
@override
Widget build(BuildContext context, WidgetRef ref) {
final targetsAsync = ref.watch(fingerprintTargetsProvider);
final settingsAsync = ref.watch(fingerprintOverrideSettingsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Fingerprint Protection'),
actions: [
MenuAnchor(
builder: (context, controller, child) {
return IconButton(
onPressed: () {
controller.open();
},
icon: const Icon(Icons.more_vert),
);
},
menuChildren: [
MenuItemButton(
leadingIcon: const Icon(MdiIcons.restore),
child: const Text('Load Defaults'),
onPressed: () async {
await ref
.read(saveEngineSettingsControllerProvider.notifier)
.save(
(currentSettings) => currentSettings.copyWith
.fingerprintingProtectionOverrides(
FingerprintOverrides.defaults().toString(),
),
);
},
),
MenuItemButton(
leadingIcon: const Icon(MdiIcons.restore),
child: const Text('Load Hardened Defaults'),
onPressed: () async {
await ref
.read(saveEngineSettingsControllerProvider.notifier)
.save(
(currentSettings) => currentSettings.copyWith
.fingerprintingProtectionOverrides(
FingerprintOverrides.hardenedDefaults()
.toString(),
),
);
},
),
],
),
],
),
body: SafeArea(
child: settingsAsync.when(
data: (result) {
return result.fold(
(overrides) {
return targetsAsync.when(
data: (targets) {
return ListView.builder(
itemCount: targets.length,
itemBuilder: (context, index) {
final target = targets[index];
final state = overrides.targets[target.name];
return CheckboxListTile.adaptive(
value:
(overrides.allTargets == true &&
state != false) ||
state == true,
onChanged: (value) async {
if (value != null) {
final newOverrides = overrides
.copyWithTarget(target.name, value)
.toString();
await ref
.read(
saveEngineSettingsControllerProvider
.notifier,
)
.save(
(currentSettings) => currentSettings
.copyWith
.fingerprintingProtectionOverrides(
newOverrides,
),
);
}
},
title: Text(target.name),
subtitle: target.description.mapNotNull(
(desc) => Text(desc),
),
);
},
);
},
error: (error, stackTrace) {
return Center(
child: FailureWidget(
exception: error,
onRetry: () {
ref.invalidate(fingerprintTargetsProvider);
},
),
);
},
loading: () =>
const Center(child: CircularProgressIndicator()),
);
},
onFailure: (errorMessage) {
return Center(
child: FailureWidget(
title: errorMessage.message,
exception: errorMessage.details,
onRetry: () {
ref.invalidate(fingerprintOverrideSettingsProvider);
},
),
);
},
);
},
error: (error, stackTrace) {
return Center(
child: FailureWidget(
exception: error,
onRetry: () {
ref.invalidate(fingerprintOverrideSettingsProvider);
},
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
),
),
);
}
}
@@ -475,6 +475,18 @@ class WebEngineSettingsScreen extends HookConsumerWidget {
await WebEngineHardeningRoute().push(context);
},
),
ListTile(
title: const Text('Fingerprint Protection'),
contentPadding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
leading: const Icon(MdiIcons.fingerprint),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
await FingerprintSettingsRoute().push(context);
},
),
],
);
},
@@ -24,6 +24,7 @@ import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart';
part 'engine_settings.g.dart';
@@ -125,6 +126,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
required this.dohProviderUrl,
required this.dohDefaultProviderUrl,
required this.dohExceptionsList,
required super.fingerprintingProtectionOverrides,
});
EngineSettings.withDefaults({
@@ -147,6 +149,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
String? dohProviderUrl,
String? dohDefaultProviderUrl,
List<String>? dohExceptionsList,
String? fingerprintingProtectionOverrides,
}) : queryParameterStripping =
queryParameterStripping ?? QueryParameterStripping.disabled,
bounceTrackingProtectionMode =
@@ -177,6 +180,9 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
webContentIsolationStrategy ??
WebContentIsolationStrategy.isolateHighValue,
enterpriseRootsEnabled: enterpriseRootsEnabled ?? false,
fingerprintingProtectionOverrides:
fingerprintingProtectionOverrides ??
FingerprintOverrides.defaults().toString(),
);
static AddonCollection? _addonCollectionFromJson(String? json) =>
@@ -201,5 +207,6 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
dohProviderUrl,
dohDefaultProviderUrl,
dohExceptionsList,
fingerprintingProtectionOverrides,
];
}
@@ -61,6 +61,10 @@ abstract class _$EngineSettingsCWProxy {
EngineSettings dohExceptionsList(List<String> dohExceptionsList);
EngineSettings fingerprintingProtectionOverrides(
String? fingerprintingProtectionOverrides,
);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
///
@@ -88,6 +92,7 @@ abstract class _$EngineSettingsCWProxy {
String dohProviderUrl,
String dohDefaultProviderUrl,
List<String> dohExceptionsList,
String? fingerprintingProtectionOverrides,
});
}
@@ -188,6 +193,13 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
EngineSettings dohExceptionsList(List<String> dohExceptionsList) =>
call(dohExceptionsList: dohExceptionsList);
@override
EngineSettings fingerprintingProtectionOverrides(
String? fingerprintingProtectionOverrides,
) => call(
fingerprintingProtectionOverrides: fingerprintingProtectionOverrides,
);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
@@ -218,6 +230,7 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
Object? dohProviderUrl = const $CopyWithPlaceholder(),
Object? dohDefaultProviderUrl = const $CopyWithPlaceholder(),
Object? dohExceptionsList = const $CopyWithPlaceholder(),
Object? fingerprintingProtectionOverrides = const $CopyWithPlaceholder(),
}) {
return EngineSettings(
javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder()
@@ -319,6 +332,11 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
? _value.dohExceptionsList
// ignore: cast_nullable_to_non_nullable
: dohExceptionsList as List<String>,
fingerprintingProtectionOverrides:
fingerprintingProtectionOverrides == const $CopyWithPlaceholder()
? _value.fingerprintingProtectionOverrides
// ignore: cast_nullable_to_non_nullable
: fingerprintingProtectionOverrides as String?,
);
}
}
@@ -388,12 +406,16 @@ EngineSettings _$EngineSettingsFromJson(Map<String, dynamic> json) =>
dohExceptionsList: (json['dohExceptionsList'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
fingerprintingProtectionOverrides:
json['fingerprintingProtectionOverrides'] as String?,
);
Map<String, dynamic> _$EngineSettingsToJson(
EngineSettings instance,
) => <String, dynamic>{
'userAgent': instance.userAgent,
'fingerprintingProtectionOverrides':
instance.fingerprintingProtectionOverrides,
'javascriptEnabled': instance.javascriptEnabled,
'trackingProtectionPolicy':
_$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy]!,
@@ -0,0 +1,27 @@
import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'rfp_target.g.dart';
@JsonSerializable()
class RFPTarget with FastEquatable {
final String name;
final int id;
final String? description;
final List<String> keywords;
RFPTarget({
required this.name,
required this.id,
this.description,
required this.keywords,
});
factory RFPTarget.fromJson(Map<String, dynamic> json) =>
_$RFPTargetFromJson(json);
Map<String, dynamic> toJson() => _$RFPTargetToJson(this);
@override
List<Object?> get hashParameters => [name, id, description, keywords];
}
@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'rfp_target.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
RFPTarget _$RFPTargetFromJson(Map<String, dynamic> json) => RFPTarget(
name: json['name'] as String,
id: (json['id'] as num).toInt(),
description: json['description'] as String?,
keywords: (json['keywords'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$RFPTargetToJson(RFPTarget instance) => <String, dynamic>{
'name': instance.name,
'id': instance.id,
'description': instance.description,
'keywords': instance.keywords,
};
@@ -0,0 +1,174 @@
import 'package:exceptions/exceptions.dart';
import 'package:fast_equatable/fast_equatable.dart';
class FingerprintOverrides with FastEquatable {
static final pattern = RegExp('([+-])([a-zA-Z_][a-zA-Z0-9_]{1,64})');
final bool? allTargets;
final Map<String, bool> targets;
FingerprintOverrides(this.allTargets, this.targets);
//Monitor https://searchfox.org/firefox-main/source/toolkit/components/resistfingerprinting/RFPTargetsDefault.inc
FingerprintOverrides.defaults()
: this(false, {
'CanvasRandomization': true,
'EfficientCanvasRandomization': true,
'FontVisibilityLangPack': true,
'JSMathFdlibm': true,
'ScreenAvailToResolution': true,
'NavigatorHWConcurrencyTiered': true,
'MaxTouchPointsCollapse': true,
});
FingerprintOverrides.hardenedDefaults()
: this(false, {
'TouchEvents': true,
'PointerEvents': true,
'KeyboardEvents': true,
'ScreenOrientation': true,
'SpeechSynthesis': true,
'CSSPrefersReducedMotion': true,
'CSSPrefersContrast': true,
'CanvasRandomization': true,
'CanvasExtractionFromThirdPartiesIsBlocked': true,
'JSLocale': true,
'NavigatorAppVersion': true,
'NavigatorBuildID': true,
'NavigatorHWConcurrency': true,
'NavigatorOscpu': true,
'NavigatorPlatform': true,
'NavigatorUserAgent': true,
'PointerId': true,
'StreamVideoFacingMode': true,
'JSDateTimeUTC': true,
'JSMathFdlibm': true,
'Gamepad': true,
'HttpUserAgent': true,
'WindowOuterSize': true,
'WindowScreenXY': true,
'WindowInnerScreenXY': true,
'ScreenPixelDepth': true,
'ScreenRect': true,
'ScreenAvailRect': true,
'VideoElementMozFrames': true,
'VideoElementMozFrameDelay': true,
'VideoElementPlaybackQuality': true,
'ReduceTimerPrecision': true,
'WidgetEvents': true,
'MediaDevices': true,
'MediaCapabilities': true,
'AudioSampleRate': true,
'NetworkConnection': true,
'WindowDevicePixelRatio': true,
'MouseEventScreenPoint': true,
'FontVisibilityBaseSystem': true,
'FontVisibilityLangPack': true,
'DeviceSensors': true,
'RoundWindowSize': true,
'UseStandinsForNativeColors': true,
'AudioContext': true,
'MediaError': true,
'DOMStyleOsxFontSmoothing': true,
'CSSDeviceSize': true,
'CSSColorInfo': true,
'CSSResolution': true,
'CSSPrefersReducedTransparency': true,
'CSSInvertedColors': true,
'CSSVideoDynamicRange': true,
'CSSPointerCapabilities': true,
'WebGLRenderCapability': true,
'WebGLRenderInfo': true,
'SiteSpecificZoom': true,
'FontVisibilityRestrictGenerics': true,
'WebVTT': true,
'WebGPULimits': true,
'WebGPUIsFallbackAdapter': true,
'WebGPUSubgroupSizes': true,
'JSLocalePrompt': true,
'ScreenAvailToResolution': true,
'UseHardcodedFontSubstitutes': true,
'DiskStorageLimit': true,
'WebCodecs': true,
'MaxTouchPoints': true,
'MaxTouchPointsCollapse': true,
'NavigatorHWConcurrencyTiered': true,
});
static Result<FingerprintOverrides> parse(
String input,
Set<String> availableTargets,
) {
final cleaned = input.replaceAll(RegExp(r'\s'), '');
if (cleaned.isEmpty) return Result.success(FingerprintOverrides(null, {}));
bool? allTargets;
final targets = <String, bool>{};
for (final word in cleaned.split(',')) {
final match = pattern.firstMatch(word);
if (match == null) {
return Result.failure(
const ErrorMessage(source: 'FpParser', message: 'Invalid Override'),
);
}
final enabled = match.group(1) != '-';
final name = match.group(2)!;
if (name == 'AllTargets') {
allTargets = enabled;
continue;
}
if (!availableTargets.contains(name)) {
return Result.failure(
const ErrorMessage(
source: 'FpParser',
message: 'Invalid target name',
),
);
}
targets[name] = enabled;
}
return Result.success(FingerprintOverrides(allTargets, targets));
}
@override
String toString() {
final sb = StringBuffer();
if (allTargets != null) {
sb.write('${allTargets! ? '+' : '-'}AllTargets');
if (targets.isNotEmpty) {
sb.write(',');
}
}
sb.write(
targets.entries.map((e) => (e.value ? '+' : '-') + e.key).join(','),
);
return sb.toString();
}
FingerprintOverrides copyWithAllTargetsEnabled(bool value) {
return FingerprintOverrides(
value,
Map.fromEntries(targets.entries.where((e) => e.value != value)),
);
}
FingerprintOverrides copyWithTarget(String name, bool value) {
if (value && allTargets == true) {
return this;
}
return FingerprintOverrides(allTargets, {...targets, name: value});
}
@override
List<Object?> get hashParameters => [allTargets, targets];
}
@@ -17,11 +17,16 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:exceptions/exceptions.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/providers.dart';
import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart';
import 'package:weblibre/features/user/domain/repositories/engine_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/features/user/domain/services/fingerprinting.dart';
part 'providers.g.dart';
@@ -47,3 +52,24 @@ bool incognitoModeEnabled(Ref ref) {
),
);
}
@Riverpod()
Future<Result<FingerprintOverrides>> fingerprintOverrideSettings(
Ref ref,
) async {
final fingerprintTargets = await ref.watch(fingerprintTargetsProvider.future);
final fingerprintTargetSet = fingerprintTargets.map((e) => e.name).toSet();
final overrides = ref.watch(
engineSettingsWithDefaultsProvider.select(
(settings) =>
settings.fingerprintingProtectionOverrides.mapNotNull(
(settings) =>
FingerprintOverrides.parse(settings, fingerprintTargetSet),
) ??
Result.success(FingerprintOverrides.defaults()),
),
);
return overrides;
}
@@ -117,3 +117,46 @@ final class IncognitoModeEnabledProvider
String _$incognitoModeEnabledHash() =>
r'36957b70a5261f9d3ad228e07cc8dd5c8f616082';
@ProviderFor(fingerprintOverrideSettings)
const fingerprintOverrideSettingsProvider =
FingerprintOverrideSettingsProvider._();
final class FingerprintOverrideSettingsProvider
extends
$FunctionalProvider<
AsyncValue<Result<FingerprintOverrides>>,
Result<FingerprintOverrides>,
FutureOr<Result<FingerprintOverrides>>
>
with
$FutureModifier<Result<FingerprintOverrides>>,
$FutureProvider<Result<FingerprintOverrides>> {
const FingerprintOverrideSettingsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'fingerprintOverrideSettingsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$fingerprintOverrideSettingsHash();
@$internal
@override
$FutureProviderElement<Result<FingerprintOverrides>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<Result<FingerprintOverrides>> create(Ref ref) {
return fingerprintOverrideSettings(ref);
}
}
String _$fingerprintOverrideSettingsHash() =>
r'd4d40ec425098fb1f5a2f0c4944f058829a41a0a';
@@ -114,6 +114,11 @@ class EngineSettingsRepository extends _$EngineSettingsRepository {
DriftSqlType.string,
db.typeMapping,
),
'fingerprintingProtectionOverrides':
settings['fingerprintingProtectionOverrides']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
});
}
@@ -34,7 +34,7 @@ final class EngineSettingsRepositoryProvider
}
String _$engineSettingsRepositoryHash() =>
r'5e403757036edb4ef964b01cdcf96cbcbdc4d809';
r'33acd89e783d8d91b9b56e86411fdfcab24a2c54';
abstract class _$EngineSettingsRepository
extends $StreamNotifier<EngineSettings> {
@@ -0,0 +1,20 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/models/rfp_target.dart';
part 'fingerprinting.g.dart';
@Riverpod(keepAlive: true)
Future<List<RFPTarget>> fingerprintTargets(Ref ref) async {
final json =
await rootBundle
.loadString('assets/preferences/rfp_targets.json')
.then(jsonDecode)
as List<dynamic>;
return json
.map((e) => RFPTarget.fromJson(e as Map<String, dynamic>))
.toList();
}
@@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fingerprinting.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(fingerprintTargets)
const fingerprintTargetsProvider = FingerprintTargetsProvider._();
final class FingerprintTargetsProvider
extends
$FunctionalProvider<
AsyncValue<List<RFPTarget>>,
List<RFPTarget>,
FutureOr<List<RFPTarget>>
>
with $FutureModifier<List<RFPTarget>>, $FutureProvider<List<RFPTarget>> {
const FingerprintTargetsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'fingerprintTargetsProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$fingerprintTargetsHash();
@$internal
@override
$FutureProviderElement<List<RFPTarget>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<RFPTarget>> create(Ref ref) {
return fingerprintTargets(ref);
}
}
String _$fingerprintTargetsHash() =>
r'1ec5933a82941b84fdad2130ccf1ba2156ddc33a';
+6 -6
View File
@@ -33,7 +33,7 @@ dependencies:
google_fonts: ^6.3.2
graphview: ^1.5.0
home_widget: ^0.8.0
hooks_riverpod: ^3.0.2
hooks_riverpod: ^3.0.3
html: ^0.15.6
http: ^1.5.0
http_parser: ^4.1.2
@@ -55,8 +55,8 @@ dependencies:
path: ../packages/pluggable_transports_proxy
pretty_qr_code: ^3.5.0
quick_actions: ^1.1.0
riverpod: ^3.0.2
riverpod_annotation: ^3.0.2
riverpod: ^3.0.3
riverpod_annotation: ^3.0.3
rss_dart: ^1.0.13
rxdart: ^0.28.0
share_plus: ^12.0.0
@@ -69,7 +69,7 @@ dependencies:
speech_to_text_google_dialog:
git:
url: https://github.com/FaFre/speech_to_text_google_dialog.git
sqlite3: ^2.9.1
sqlite3: ^2.9.2
sqlite3_flutter_libs: ^0.5.40
synchronized: ^3.4.0
text_scroll: ^0.2.1
@@ -96,8 +96,8 @@ dev_dependencies:
go_router_builder: ^4.1.0
json_serializable: ^6.11.1
lint: ^2.8.0
riverpod_generator: ^3.0.2
riverpod_lint: ^3.0.2
riverpod_generator: ^3.0.3
riverpod_lint: ^3.0.3
flutter:
uses-material-design: true