added support for custom addon collections
This commit is contained in:
@@ -38,6 +38,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/c
|
||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_list.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_selection.dart';
|
||||
import 'package:weblibre/features/onboarding/presentation/onboarding.dart';
|
||||
import 'package:weblibre/features/settings/presentation/screens/addon_collection.dart';
|
||||
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/general_settings.dart';
|
||||
|
||||
@@ -112,6 +112,13 @@ RouteBase get $settingsRoute => GoRouteData.$route(
|
||||
path: 'developer',
|
||||
name: 'DeveloperSettingsRoute',
|
||||
factory: $DeveloperSettingsRoute._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'addon_collection',
|
||||
name: 'AddonCollectionRoute',
|
||||
factory: $AddonCollectionRoute._fromState,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -268,6 +275,28 @@ mixin $DeveloperSettingsRoute on GoRouteData {
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
mixin $AddonCollectionRoute on GoRouteData {
|
||||
static AddonCollectionRoute _fromState(GoRouterState state) =>
|
||||
AddonCollectionRoute();
|
||||
|
||||
@override
|
||||
String get location =>
|
||||
GoRouteData.$location('/settings/developer/addon_collection');
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
RouteBase get $browserRoute => GoRouteData.$route(
|
||||
path: '/',
|
||||
name: 'BrowserRoute',
|
||||
|
||||
@@ -47,6 +47,12 @@ part of 'routes.dart';
|
||||
TypedGoRoute<DeveloperSettingsRoute>(
|
||||
name: 'DeveloperSettingsRoute',
|
||||
path: 'developer',
|
||||
routes: [
|
||||
TypedGoRoute<AddonCollectionRoute>(
|
||||
name: 'AddonCollectionRoute',
|
||||
path: 'addon_collection',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -85,6 +91,13 @@ class DeveloperSettingsRoute extends GoRouteData with $DeveloperSettingsRoute {
|
||||
}
|
||||
}
|
||||
|
||||
class AddonCollectionRoute extends GoRouteData with $AddonCollectionRoute {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const AddonCollectionScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class WebEngineHardeningRoute extends GoRouteData
|
||||
with $WebEngineHardeningRoute {
|
||||
@override
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:universal_io/io.dart';
|
||||
import 'package:weblibre/features/user/data/models/engine_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/engine_settings.dart';
|
||||
import 'package:weblibre/utils/form_validators.dart';
|
||||
|
||||
const _defaultServerUrl = 'https://services.addons.mozilla.org';
|
||||
|
||||
class AddonCollectionScreen extends HookConsumerWidget {
|
||||
const AddonCollectionScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
|
||||
final addonCollectionSetting = ref.watch(
|
||||
engineSettingsWithDefaultsProvider.select(
|
||||
(value) => value.addonCollection,
|
||||
),
|
||||
);
|
||||
|
||||
final serverURLController = useTextEditingController(
|
||||
text: addonCollectionSetting?.serverURL ?? _defaultServerUrl,
|
||||
keys: [addonCollectionSetting],
|
||||
);
|
||||
|
||||
final collectionUserController = useTextEditingController(
|
||||
text: addonCollectionSetting?.collectionUser,
|
||||
keys: [addonCollectionSetting],
|
||||
);
|
||||
|
||||
final collectionNameController = useTextEditingController(
|
||||
text: addonCollectionSetting?.collectionName,
|
||||
keys: [addonCollectionSetting],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Custom Extension Collection'),
|
||||
actions: [
|
||||
if (addonCollectionSetting != null)
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(engineSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.addonCollection(null),
|
||||
);
|
||||
|
||||
unawaited(
|
||||
Future.delayed(const Duration(seconds: 1)).whenComplete(() {
|
||||
exit(0);
|
||||
}),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: formKey,
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: serverURLController,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Server URL'),
|
||||
hintText: _defaultServerUrl,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
keyboardType: TextInputType.url,
|
||||
validator: (value) {
|
||||
return validateUrl(
|
||||
value,
|
||||
onlyHttpProtocol: true,
|
||||
eagerParsing: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: collectionUserController,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Collection User'),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
validator: validateRequired,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: collectionNameController,
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Collection Name'),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
validator: validateRequired,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState?.validate() == true) {
|
||||
await ref
|
||||
.read(engineSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(
|
||||
(
|
||||
currentSettings,
|
||||
) => currentSettings.copyWith.addonCollection(
|
||||
AddonCollection(
|
||||
serverURL: serverURLController.text,
|
||||
collectionUser: collectionUserController.text,
|
||||
collectionName: collectionNameController.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
unawaited(
|
||||
Future.delayed(const Duration(seconds: 1)).whenComplete(
|
||||
() {
|
||||
exit(0);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Save & Restart Browser'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:universal_io/io.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
|
||||
import 'package:weblibre/features/settings/presentation/widgets/custom_list_tile.dart';
|
||||
import 'package:weblibre/features/user/data/models/engine_settings.dart';
|
||||
@@ -198,6 +199,17 @@ class DeveloperSettingsScreen extends HookConsumerWidget {
|
||||
label: const Text('Copy'),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.puzzle),
|
||||
title: const Text('Custom Extension Collection'),
|
||||
subtitle: const Text(
|
||||
'Custom Add-on Collections are curated lists of extensions that users can create and share.',
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await AddonCollectionRoute().push(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
* 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 'dart:convert';
|
||||
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
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';
|
||||
|
||||
part 'engine_settings.g.dart';
|
||||
|
||||
@@ -60,6 +63,9 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
|
||||
|
||||
final BounceTrackingProtectionMode bounceTrackingProtectionMode;
|
||||
|
||||
@JsonKey(fromJson: _addonCollectionFromJson, toJson: _addonCollectionToJson)
|
||||
final AddonCollection? addonCollection;
|
||||
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ContentBlocking get contentBlocking => ContentBlocking(
|
||||
@@ -85,6 +91,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
|
||||
required super.enterpriseRootsEnabled,
|
||||
required this.queryParameterStripping,
|
||||
required this.bounceTrackingProtectionMode,
|
||||
required this.addonCollection,
|
||||
});
|
||||
|
||||
EngineSettings.withDefaults({
|
||||
@@ -102,6 +109,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
|
||||
BounceTrackingProtectionMode? bounceTrackingProtectionMode,
|
||||
super.userAgent,
|
||||
bool? enterpriseRootsEnabled,
|
||||
this.addonCollection,
|
||||
}) : queryParameterStripping =
|
||||
queryParameterStripping ?? QueryParameterStripping.disabled,
|
||||
bounceTrackingProtectionMode =
|
||||
@@ -129,6 +137,14 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
|
||||
enterpriseRootsEnabled: enterpriseRootsEnabled ?? false,
|
||||
);
|
||||
|
||||
static AddonCollection? _addonCollectionFromJson(String? json) =>
|
||||
json.mapNotNull(
|
||||
(collection) => AddonCollection.decode(jsonDecode(collection) as List),
|
||||
);
|
||||
|
||||
static String? _addonCollectionToJson(AddonCollection? collection) =>
|
||||
collection.mapNotNull((collection) => jsonEncode(collection.encode()));
|
||||
|
||||
factory EngineSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$EngineSettingsFromJson(json);
|
||||
|
||||
@@ -136,19 +152,8 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
super.javascriptEnabled,
|
||||
super.trackingProtectionPolicy,
|
||||
super.httpsOnlyMode,
|
||||
super.globalPrivacyControlEnabled,
|
||||
super.preferredColorScheme,
|
||||
super.cookieBannerHandlingMode,
|
||||
super.cookieBannerHandlingModePrivateBrowsing,
|
||||
super.cookieBannerHandlingGlobalRules,
|
||||
super.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
super.webContentIsolationStrategy,
|
||||
super.userAgent,
|
||||
queryParameterStripping,
|
||||
bounceTrackingProtectionMode,
|
||||
super.enterpriseRootsEnabled,
|
||||
addonCollection,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ abstract class _$EngineSettingsCWProxy {
|
||||
BounceTrackingProtectionMode bounceTrackingProtectionMode,
|
||||
);
|
||||
|
||||
EngineSettings addonCollection(AddonCollection? addonCollection);
|
||||
|
||||
/// 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)`.
|
||||
///
|
||||
@@ -73,6 +75,7 @@ abstract class _$EngineSettingsCWProxy {
|
||||
bool? enterpriseRootsEnabled,
|
||||
QueryParameterStripping queryParameterStripping,
|
||||
BounceTrackingProtectionMode bounceTrackingProtectionMode,
|
||||
AddonCollection? addonCollection,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,6 +156,10 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
|
||||
BounceTrackingProtectionMode bounceTrackingProtectionMode,
|
||||
) => call(bounceTrackingProtectionMode: bounceTrackingProtectionMode);
|
||||
|
||||
@override
|
||||
EngineSettings addonCollection(AddonCollection? addonCollection) =>
|
||||
call(addonCollection: addonCollection);
|
||||
|
||||
@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)`.
|
||||
@@ -178,6 +185,7 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
|
||||
Object? enterpriseRootsEnabled = const $CopyWithPlaceholder(),
|
||||
Object? queryParameterStripping = const $CopyWithPlaceholder(),
|
||||
Object? bounceTrackingProtectionMode = const $CopyWithPlaceholder(),
|
||||
Object? addonCollection = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return EngineSettings(
|
||||
javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder()
|
||||
@@ -251,6 +259,10 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
|
||||
? _value.bounceTrackingProtectionMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: bounceTrackingProtectionMode as BounceTrackingProtectionMode,
|
||||
addonCollection: addonCollection == const $CopyWithPlaceholder()
|
||||
? _value.addonCollection
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: addonCollection as AddonCollection?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -308,6 +320,9 @@ EngineSettings _$EngineSettingsFromJson(Map<String, dynamic> json) =>
|
||||
),
|
||||
userAgent: json['userAgent'] as String?,
|
||||
enterpriseRootsEnabled: json['enterpriseRootsEnabled'] as bool?,
|
||||
addonCollection: EngineSettings._addonCollectionFromJson(
|
||||
json['addonCollection'] as String?,
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$EngineSettingsToJson(
|
||||
@@ -337,6 +352,9 @@ Map<String, dynamic> _$EngineSettingsToJson(
|
||||
'bounceTrackingProtectionMode':
|
||||
_$BounceTrackingProtectionModeEnumMap[instance
|
||||
.bounceTrackingProtectionMode]!,
|
||||
'addonCollection': EngineSettings._addonCollectionToJson(
|
||||
instance.addonCollection,
|
||||
),
|
||||
};
|
||||
|
||||
const _$TrackingProtectionPolicyEnumMap = {
|
||||
|
||||
@@ -94,6 +94,10 @@ class EngineSettingsRepository extends _$EngineSettingsRepository {
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
),
|
||||
'addonCollection': settings['addonCollection']?.readAs(
|
||||
DriftSqlType.string,
|
||||
db.typeMapping,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ final class EngineSettingsRepositoryProvider
|
||||
}
|
||||
|
||||
String _$engineSettingsRepositoryHash() =>
|
||||
r'd0c2fbe060b5eaee7189f6cb0dd58c97cf5987b5';
|
||||
r'8cf0a615f825ddc6411c2985d2cdc19ef31251cb';
|
||||
|
||||
abstract class _$EngineSettingsRepository
|
||||
extends $StreamNotifier<EngineSettings> {
|
||||
|
||||
@@ -91,6 +91,7 @@ void main() async {
|
||||
await GeckoBrowserService().initialize(
|
||||
kDebugMode ? LogLevel.debug : LogLevel.warn,
|
||||
engineSettings.contentBlocking,
|
||||
engineSettings.addonCollection,
|
||||
);
|
||||
|
||||
await ref
|
||||
|
||||
@@ -47,3 +47,11 @@ String? validateUrl(
|
||||
|
||||
return 'Inavlid URL';
|
||||
}
|
||||
|
||||
String? validateRequired(String? value, {String message = 'Value required'}) {
|
||||
if (value.isNotEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user