intermediate prefs
This commit is contained in:
@@ -169,7 +169,8 @@
|
||||
|
||||
<service
|
||||
android:name="eu.lensai.flutter_mozilla_components.services.DownloadService"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false" />
|
||||
|
||||
<service android:name="eu.lensai.flutter_mozilla_components.services.MediaSessionService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
{
|
||||
"system": {
|
||||
"incognito_mode": {
|
||||
"preferences": {
|
||||
"privacy.sanitize.sanitizeOnShutdown": true,
|
||||
"privacy.clearOnShutdown.cache": true,
|
||||
"privacy.clearOnShutdown_v2.cache": true,
|
||||
"privacy.clearOnShutdown.downloads": true,
|
||||
"privacy.clearOnShutdown.formdata": true,
|
||||
"privacy.clearOnShutdown.history": true,
|
||||
"privacy.clearOnShutdown_v2.historyFormDataAndDownloads": true,
|
||||
"privacy.clearOnShutdown.cookies": true,
|
||||
"privacy.clearOnShutdown.offlineApps": true,
|
||||
"privacy.clearOnShutdown.sessions": true,
|
||||
"privacy.clearOnShutdown_v2.cookiesAndStorage": true,
|
||||
"privacy.clearSiteData.cache": true,
|
||||
"privacy.clearSiteData.cookiesAndStorage": false,
|
||||
"privacy.clearSiteData.historyFormDataAndDownloads": true,
|
||||
"privacy.cpd.cache": true,
|
||||
"privacy.clearHistory.cache": true,
|
||||
"privacy.cpd.formdata": true,
|
||||
"privacy.cpd.history": true,
|
||||
"privacy.clearHistory.historyFormDataAndDownloads": true,
|
||||
"privacy.cpd.cookies": false,
|
||||
"privacy.cpd.sessions": true,
|
||||
"privacy.cpd.offlineApps": false,
|
||||
"privacy.clearHistory.cookiesAndStorage": false,
|
||||
"privacy.sanitize.timeSpan": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"Safe Browsing": {
|
||||
"description": "Google Safe Browsing is a service from Google that warns users when they attempt to navigate to a dangerous website or download dangerous files.",
|
||||
"preferences": {
|
||||
"browser.safebrowsing.downloads.remote.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable Google Safe Browsing for downloads",
|
||||
"description": "To verify the safety of certain executable files, GeckoView may submit some information about the file, including the name, origin, size and a cryptographic hash of the contents, to the Google Safe Browsing service which helps GeckoView determine whether or not the file should be blocked."
|
||||
}
|
||||
}
|
||||
},
|
||||
"Block Implicit Outbound": {
|
||||
"description": "Control not explicitly user invoked interactions",
|
||||
"preferences": {
|
||||
"network.prefetch-next": {
|
||||
"value": false,
|
||||
"title": "Disable link prefetching"
|
||||
},
|
||||
"network.dns.disablePrefetch": {
|
||||
"value": true,
|
||||
"title": "Disable DNS prefetching"
|
||||
},
|
||||
"network.dns.disablePrefetchFromHTTPS": {
|
||||
"value": true,
|
||||
"title": "Disable HTTPS DNS prefetching"
|
||||
},
|
||||
"network.predictor.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable prefetching predictor"
|
||||
},
|
||||
"network.predictor.enable-prefetch": {
|
||||
"value": false,
|
||||
"title": "Disable prefetching predictions"
|
||||
},
|
||||
"network.http.speculative-parallel-limit": {
|
||||
"value": 0,
|
||||
"title": "Disable link-mouseover opening connection to linked server"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DNS": {
|
||||
"description": "Improve DNS and Proxy/SOCKS security",
|
||||
"preferences": {
|
||||
"network.trr.mode": {
|
||||
"value": 3,
|
||||
"title": "Enable DNS over HTTPS",
|
||||
"requireUserOptIn": true
|
||||
},
|
||||
"network.trr.uri": {
|
||||
"value": "https://dns.quad9.net/dns-query",
|
||||
"title": "Use quad9 for Trusted Recursive Resolver (TRR)",
|
||||
"description": "Quad9 is a privacy-focused DNS resolver that provides malware blocking and security features.",
|
||||
"requireUserOptIn": true
|
||||
},
|
||||
"network.trr.custom.uri": {
|
||||
"value": "https://dns.quad9.net/dns-query",
|
||||
"title": "Use quad9 for custom Trusted Recursive Resolver (TRR)",
|
||||
"description": "Quad9 is a privacy-focused DNS resolver that provides malware blocking and security features.",
|
||||
"requireUserOptIn": true
|
||||
},
|
||||
"network.dns.native_https_query": {
|
||||
"value": true,
|
||||
"title": "Enable native DNS HTTPS Query",
|
||||
"description": "DNS over HTTPS",
|
||||
"shouldBeDefault": true
|
||||
},
|
||||
"network.proxy.socks_remote_dns": {
|
||||
"value": true,
|
||||
"title": "Set the proxy server to do any DNS lookups when using SOCKS",
|
||||
"description": "e.g. in Tor, this stops your local DNS server from knowing your Tor destination as a remote Tor node will handle the DNS request"
|
||||
},
|
||||
"network.file.disable_unc_paths": {
|
||||
"value": true,
|
||||
"title": "Disable using UNC (Uniform Naming Convention) paths",
|
||||
"description": "Warning: Can break extensions for profiles on network shares",
|
||||
"requireUserOptIn": true
|
||||
},
|
||||
"network.gio.supported-protocols": {
|
||||
"value": "",
|
||||
"title": "Disable GIO as a potential proxy bypass vector"
|
||||
},
|
||||
"media.peerconnection.ice.proxy_only_if_behind_proxy": {
|
||||
"value": true,
|
||||
"title": "Force WebRTC inside the proxy"
|
||||
},
|
||||
"media.peerconnection.ice.default_address_only": {
|
||||
"value": true,
|
||||
"title": "Force a single network interface for ICE candidates generation",
|
||||
"description": "When using a system-wide proxy, it uses the proxy interface"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Disk Avoidance": {
|
||||
"description": "Control what data to persist to disk",
|
||||
"preferences": {
|
||||
"browser.cache.disk.enable": {
|
||||
"value": false,
|
||||
"title": "Disable disk cache",
|
||||
"description": "Don't cache data to disk. Might impact performance negatively",
|
||||
"requireUserOptIn": true
|
||||
},
|
||||
"browser.privatebrowsing.forceMediaMemoryCache": {
|
||||
"value": true,
|
||||
"title": "Set media cache in Private Browsing to in-memory"
|
||||
},
|
||||
"media.memory_cache_max_size": {
|
||||
"value": 65536,
|
||||
"title": "Increase in-memory media cache maximum size"
|
||||
},
|
||||
"browser.sessionstore.privacy_level": {
|
||||
"value": 2,
|
||||
"title": "Disable storing extra session data",
|
||||
"description": "Extra session data such as form content, cookies and POST data"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Secure Connections": {
|
||||
"description": "SSL/TLS / OCSP / CERTS / HPKP",
|
||||
"preferences": {
|
||||
"security.ssl.require_safe_negotiation": {
|
||||
"value": true,
|
||||
"title": "Require safe negotiation",
|
||||
"description": "Blocks connections to servers that don't support RFC 5746 as they're potentially vulnerable to a MiTM attack."
|
||||
},
|
||||
"security.tls.enable_0rtt_data": {
|
||||
"value": false,
|
||||
"title": "Disable TLS1.3 0-RTT (round-trip time)",
|
||||
"description": "This data is not forward secret, as it is encrypted solely under keys derived using the offered PSK. There are no guarantees of non-replay between connections."
|
||||
},
|
||||
"security.OCSP.enabled": {
|
||||
"value": 1,
|
||||
"title": "Enforce OCSP fetching to confirm current validity of certificates"
|
||||
},
|
||||
"security.OCSP.require": {
|
||||
"value": true,
|
||||
"title": "Set OCSP fetch failures to hard-fail",
|
||||
"description": "When a CA cannot be reached to validate a cert, GeckoView just continues the connection (=soft-fail). Setting this pref to true tells GeckoView to instead terminate the connection (=hard-fail)."
|
||||
},
|
||||
"security.cert_pinning.enforcement_level": {
|
||||
"value": 2,
|
||||
"title": "Enable strict PKP (Public Key Pinning)"
|
||||
},
|
||||
"security.remote_settings.crlite_filters.enabled": {
|
||||
"value": true,
|
||||
"title": "Enable CRLite"
|
||||
},
|
||||
"security.pki.crlite_mode": {
|
||||
"value": 2,
|
||||
"title": "Enable CRLite for PKI"
|
||||
},
|
||||
"security.tls.enable_kyber": {
|
||||
"value": true,
|
||||
"title": "Enable Post-Quantum Kyber TLS"
|
||||
},
|
||||
"network.http.http3.enable_kyber": {
|
||||
"value": true,
|
||||
"title": "Enable HTTP3 Post-Quantum Kyber TLS"
|
||||
},
|
||||
"security.ssl.treat_unsafe_negotiation_as_broken": {
|
||||
"value": true,
|
||||
"title": "Display warning on the padlock for 'broken security'"
|
||||
},
|
||||
"browser.xul.error_pages.expert_bad_cert": {
|
||||
"value": true,
|
||||
"title": "Display advanced information on Insecure Connection warning pages"
|
||||
},
|
||||
"dom.security.https_first": {
|
||||
"value": true,
|
||||
"title": "Attempt to establish HTTPS connections first before falling back to HTTP",
|
||||
"shouldBeDefault": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Miscelaneous": {
|
||||
"preferences": {
|
||||
"permissions.manager.defaultsUrl": {
|
||||
"value": "",
|
||||
"title": "Remove special permissions for certain mozilla domains"
|
||||
},
|
||||
"network.IDN_show_punycode": {
|
||||
"value": true,
|
||||
"title": "Use Punycode in Internationalized Domain Names to eliminate possible spoofing"
|
||||
},
|
||||
"extensions.enabledScopes": {
|
||||
"value": 5,
|
||||
"title": "Limit allowed extension directories",
|
||||
"description": "Only allow profile and application directories",
|
||||
"shouldBeDefault": true
|
||||
},
|
||||
"extensions.postDownloadThirdPartyPrompt": {
|
||||
"value": false,
|
||||
"title": "Disable bypassing 3rd party extension install prompts"
|
||||
},
|
||||
"pdfjs.enableScripting": {
|
||||
"value": false,
|
||||
"title": "Disable PDFJS scripting"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Privacy": {
|
||||
"preferences": {
|
||||
"privacy.globalprivacycontrol.enabled": {
|
||||
"value": true,
|
||||
"title": "Enable Global Privacy Control (GPC)",
|
||||
"shouldBeDefault": true
|
||||
},
|
||||
"browser.contentblocking.category": {
|
||||
"value": "strict",
|
||||
"title": "Enable ETP Strict Mode",
|
||||
"description": "ETP Strict Mode enables Total Cookie Protection (TCP)"
|
||||
},
|
||||
"network.http.referer.XOriginTrimmingPolicy": {
|
||||
"value": 2,
|
||||
"title": "Trim cross-origin referrers",
|
||||
"description": "Trims down referrers to just the scheme, hostname and port"
|
||||
},
|
||||
"general.useragent.updates.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable automatic User Agent updates",
|
||||
"description": "This preference controls whether Firefox automatically updates its User-Agent string. When set to false, it prevents Firefox from automatically updating the browser's User-Agent string, which is a text identifier that tells websites which browser and version you're using."
|
||||
},
|
||||
"dom.private-attribution.submission.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable Private Attribution Submission Control",
|
||||
"shouldBeDefault": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:langchain_openai/langchain_openai.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -18,7 +16,7 @@ part 'models.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ChatOpenAI chatModel(Ref ref) {
|
||||
final openaiApiKey = 'FfTBKW1t5dOOXVoiaF2hbXPAHiz3fvfy';
|
||||
const openaiApiKey = 'FfTBKW1t5dOOXVoiaF2hbXPAHiz3fvfy';
|
||||
return ChatOpenAI(
|
||||
apiKey: openaiApiKey,
|
||||
baseUrl: 'https://api.deepinfra.com/v1/openai',
|
||||
@@ -30,7 +28,7 @@ ChatOpenAI chatModel(Ref ref) {
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
OpenAIEmbeddings embeddingModel(Ref ref) {
|
||||
final openaiApiKey = 'FfTBKW1t5dOOXVoiaF2hbXPAHiz3fvfy';
|
||||
const openaiApiKey = 'FfTBKW1t5dOOXVoiaF2hbXPAHiz3fvfy';
|
||||
return OpenAIEmbeddings(
|
||||
apiKey: openaiApiKey,
|
||||
baseUrl: 'https://api.deepinfra.com/v1/openai',
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'models.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatModelHash() => r'e7538b9f0dd43ea64953164c5402e76fde96f5e9';
|
||||
String _$chatModelHash() => r'9e44e8fa1d688968841933aac8e13212cf41f4ed';
|
||||
|
||||
/// See also [chatModel].
|
||||
@ProviderFor(chatModel)
|
||||
@@ -22,7 +22,7 @@ final chatModelProvider = Provider<ChatOpenAI>.internal(
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ChatModelRef = ProviderRef<ChatOpenAI>;
|
||||
String _$embeddingModelHash() => r'5d65c75df195a26adefac5a75f0e8f6b0f1b8590';
|
||||
String _$embeddingModelHash() => r'40111a5f08b43afe6edd399ff0e6c022b3387265';
|
||||
|
||||
/// See also [embeddingModel].
|
||||
@ProviderFor(embeddingModel)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/data/models/drag_data.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -12,3 +13,19 @@ GoRouter router(Ref ref) {
|
||||
routes: $appRoutes,
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
class WillAcceptDrop extends _$WillAcceptDrop {
|
||||
void setData(DropTargetData data) {
|
||||
state = data;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
state = null;
|
||||
}
|
||||
|
||||
@override
|
||||
DropTargetData? build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,5 +22,21 @@ final routerProvider = Provider<GoRouter>.internal(
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef RouterRef = ProviderRef<GoRouter>;
|
||||
String _$willAcceptDropHash() => r'97b47784ef9101757602b4408d1f545ef2308830';
|
||||
|
||||
/// See also [WillAcceptDrop].
|
||||
@ProviderFor(WillAcceptDrop)
|
||||
final willAcceptDropProvider =
|
||||
AutoDisposeNotifierProvider<WillAcceptDrop, DropTargetData?>.internal(
|
||||
WillAcceptDrop.new,
|
||||
name: r'willAcceptDropProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$willAcceptDropHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$WillAcceptDrop = AutoDisposeNotifier<DropTargetData?>;
|
||||
// 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
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/screens/browser.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_list.dart';
|
||||
import 'package:lensai/features/search/presentation/screens/search.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/browser_hardening.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/browser_hardening_group.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
||||
import 'package:lensai/features/user/presentation/screens/auth.dart';
|
||||
|
||||
@@ -180,6 +182,18 @@ class ContainerListRoute extends GoRouteData {
|
||||
@TypedGoRoute<SettingsRoute>(
|
||||
name: 'SettingsRoute',
|
||||
path: '/settings',
|
||||
routes: [
|
||||
TypedGoRoute<BrowserHardeningRoute>(
|
||||
name: 'BrowserHardeningRoute',
|
||||
path: 'hardening',
|
||||
routes: [
|
||||
TypedGoRoute<BrowserHardeningGroupRoute>(
|
||||
name: 'BrowserHardeningGroupRoute',
|
||||
path: 'group/:group',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
class SettingsRoute extends GoRouteData {
|
||||
@override
|
||||
@@ -188,6 +202,24 @@ class SettingsRoute extends GoRouteData {
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserHardeningRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const BrowserHardeningScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserHardeningGroupRoute extends GoRouteData {
|
||||
final String group;
|
||||
|
||||
const BrowserHardeningGroupRoute({required this.group});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return BrowserHardeningGroupScreen(groupName: group);
|
||||
}
|
||||
}
|
||||
|
||||
@TypedGoRoute<ChatArchiveListRoute>(
|
||||
name: 'ChatArchiveListRoute',
|
||||
path: '/chat_archive',
|
||||
|
||||
@@ -259,6 +259,20 @@ RouteBase get $settingsRoute => GoRouteData.$route(
|
||||
path: '/settings',
|
||||
name: 'SettingsRoute',
|
||||
factory: $SettingsRouteExtension._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'hardening',
|
||||
name: 'BrowserHardeningRoute',
|
||||
factory: $BrowserHardeningRouteExtension._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'group/:group',
|
||||
name: 'BrowserHardeningGroupRoute',
|
||||
factory: $BrowserHardeningGroupRouteExtension._fromState,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
extension $SettingsRouteExtension on SettingsRoute {
|
||||
@@ -278,6 +292,44 @@ extension $SettingsRouteExtension on SettingsRoute {
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
extension $BrowserHardeningRouteExtension on BrowserHardeningRoute {
|
||||
static BrowserHardeningRoute _fromState(GoRouterState state) =>
|
||||
BrowserHardeningRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/hardening',
|
||||
);
|
||||
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
extension $BrowserHardeningGroupRouteExtension on BrowserHardeningGroupRoute {
|
||||
static BrowserHardeningGroupRoute _fromState(GoRouterState state) =>
|
||||
BrowserHardeningGroupRoute(
|
||||
group: state.pathParameters['group']!,
|
||||
);
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/hardening/group/${Uri.encodeComponent(group)}',
|
||||
);
|
||||
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
RouteBase get $chatArchiveListRoute => GoRouteData.$route(
|
||||
path: '/chat_archive',
|
||||
name: 'ChatArchiveListRoute',
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
|
||||
sealed class DropTargetData with FastEquatable {}
|
||||
|
||||
final class ContainerDropData extends DropTargetData {
|
||||
final String tabId;
|
||||
|
||||
ContainerDropData(this.tabId);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [tabId];
|
||||
}
|
||||
|
||||
final class DeleteDropData extends DropTargetData {
|
||||
final String tabId;
|
||||
|
||||
DeleteDropData(this.tabId);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [tabId];
|
||||
}
|
||||
|
||||
sealed class DragTargetData with FastEquatable {}
|
||||
|
||||
final class TabDragData extends DragTargetData {
|
||||
final String tabId;
|
||||
|
||||
TabDragData(this.tabId);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [tabId];
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/search/domain/providers/search_suggestions.dart';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
import 'package:langchain/langchain.dart';
|
||||
|
||||
@@ -4,7 +4,9 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/providers.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/data/models/drag_data.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
@@ -16,7 +18,6 @@ import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/create_tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_view.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
||||
@@ -164,7 +165,8 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
// preferredTool: KagiTool.search,
|
||||
// ),
|
||||
// );
|
||||
await context.push(SearchRoute().location);
|
||||
await context
|
||||
.push(const SearchRoute().location);
|
||||
},
|
||||
icon: Icon(KagiTool.search.icon),
|
||||
),
|
||||
@@ -588,13 +590,19 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final x =
|
||||
await context.push(UserAuthRoute().location);
|
||||
print(x);
|
||||
await context.push(UserAuthRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.info),
|
||||
child: const Text('Auth'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context
|
||||
.push(BrowserHardeningRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.info),
|
||||
child: const Text('Pref'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -603,169 +611,190 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
body: OverlayPortal(
|
||||
controller: overlayController,
|
||||
overlayChildBuilder: (context) {
|
||||
return displayedOverlayDialog!;
|
||||
body: DragTarget<TabDragData>(
|
||||
onMove: (details) {
|
||||
ref
|
||||
.read(willAcceptDropProvider.notifier)
|
||||
.setData(DeleteDropData(details.data.tabId));
|
||||
},
|
||||
child: Listener(
|
||||
onPointerDown: (displayedSheet != null)
|
||||
? (_) {
|
||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||
}
|
||||
: null,
|
||||
child: HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
return BackButtonListener(
|
||||
onBackButtonPressed: () async {
|
||||
final tabState = (selectedTabId != null)
|
||||
? ref.read(tabStateProvider(selectedTabId))
|
||||
: null;
|
||||
|
||||
final tabCount =
|
||||
ref.read(tabListProvider.select((tabs) => tabs.length));
|
||||
|
||||
//Don't do anything if a child route is active
|
||||
if (GoRouterState.of(context).topRoute?.path !=
|
||||
BrowserRoute().location) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (displayedSheet != null) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (displayedOverlayDialog != null) {
|
||||
ref
|
||||
.read(overlayDialogControllerProvider.notifier)
|
||||
.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tabState?.isFullScreen == true) {
|
||||
await ref
|
||||
.read(selectedTabSessionNotifierProvider)
|
||||
.exitFullscreen();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tabState?.historyState.canGoBack == true) {
|
||||
lastBackButtonPress.value = null;
|
||||
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
|
||||
await controller.goBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastBackButtonPress.value != null &&
|
||||
DateTime.now().difference(lastBackButtonPress.value!) <
|
||||
const Duration(seconds: 2)) {
|
||||
lastBackButtonPress.value = null;
|
||||
|
||||
if (tabState != null && tabCount > 1) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(tabState.id);
|
||||
return true;
|
||||
} else {
|
||||
//Mark back as unhandled and navigator will pop
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
lastBackButtonPress.value = DateTime.now();
|
||||
ScaffoldMessenger.of(context)
|
||||
..clearSnackBars()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: (tabCount > 1)
|
||||
? const Text(
|
||||
'Please click BACK again to close current tab',
|
||||
)
|
||||
: const Text(
|
||||
'Please click BACK again to exit app',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
const BrowserView(),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final value = ref.watch(
|
||||
selectedTabStateProvider.select(
|
||||
(state) {
|
||||
if (state?.isLoading == true) {
|
||||
return state?.progress ?? 100;
|
||||
}
|
||||
|
||||
//When not loading we assumed finished
|
||||
return 100;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return Visibility(
|
||||
visible: value < 100,
|
||||
child: LinearProgressIndicator(
|
||||
value: value / 100,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final value = ref.watch(
|
||||
selectedTabStateProvider.select(
|
||||
(state) => EdgeInsets.only(
|
||||
bottom: (state?.isLoading == true &&
|
||||
state?.progress != null &&
|
||||
state!.progress < 100)
|
||||
? 4.0
|
||||
: 0.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return FindInPageWidget(padding: value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (displayedSheet != null)
|
||||
ModalBarrier(
|
||||
color: Theme.of(context).dialogTheme.barrierColor ??
|
||||
Colors.black54,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
onLeave: (data) {
|
||||
ref.read(willAcceptDropProvider.notifier).clear();
|
||||
},
|
||||
onAcceptWithDetails: (details) async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(details.data.tabId);
|
||||
},
|
||||
builder: (context, _, __) {
|
||||
return OverlayPortal(
|
||||
controller: overlayController,
|
||||
overlayChildBuilder: (context) {
|
||||
return displayedOverlayDialog!;
|
||||
},
|
||||
),
|
||||
),
|
||||
child: Listener(
|
||||
onPointerDown: (displayedSheet != null)
|
||||
? (_) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.dismiss();
|
||||
}
|
||||
: null,
|
||||
child: HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
return BackButtonListener(
|
||||
onBackButtonPressed: () async {
|
||||
final tabState = (selectedTabId != null)
|
||||
? ref.read(tabStateProvider(selectedTabId))
|
||||
: null;
|
||||
|
||||
final tabCount = ref.read(
|
||||
tabListProvider.select((tabs) => tabs.length));
|
||||
|
||||
//Don't do anything if a child route is active
|
||||
if (GoRouterState.of(context).topRoute?.path !=
|
||||
BrowserRoute().location) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (displayedSheet != null) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (displayedOverlayDialog != null) {
|
||||
ref
|
||||
.read(overlayDialogControllerProvider.notifier)
|
||||
.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tabState?.isFullScreen == true) {
|
||||
await ref
|
||||
.read(selectedTabSessionNotifierProvider)
|
||||
.exitFullscreen();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tabState?.historyState.canGoBack == true) {
|
||||
lastBackButtonPress.value = null;
|
||||
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
|
||||
await controller.goBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastBackButtonPress.value != null &&
|
||||
DateTime.now()
|
||||
.difference(lastBackButtonPress.value!) <
|
||||
const Duration(seconds: 2)) {
|
||||
lastBackButtonPress.value = null;
|
||||
|
||||
if (tabState != null && tabCount > 1) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(tabState.id);
|
||||
return true;
|
||||
} else {
|
||||
//Mark back as unhandled and navigator will pop
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
lastBackButtonPress.value = DateTime.now();
|
||||
ScaffoldMessenger.of(context)
|
||||
..clearSnackBars()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: (tabCount > 1)
|
||||
? const Text(
|
||||
'Please click BACK again to close current tab',
|
||||
)
|
||||
: const Text(
|
||||
'Please click BACK again to exit app',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
const BrowserView(),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final value = ref.watch(
|
||||
selectedTabStateProvider.select(
|
||||
(state) {
|
||||
if (state?.isLoading == true) {
|
||||
return state?.progress ?? 100;
|
||||
}
|
||||
|
||||
//When not loading we assumed finished
|
||||
return 100;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return Visibility(
|
||||
visible: value < 100,
|
||||
child: LinearProgressIndicator(
|
||||
value: value / 100,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final value = ref.watch(
|
||||
selectedTabStateProvider.select(
|
||||
(state) => EdgeInsets.only(
|
||||
bottom: (state?.isLoading == true &&
|
||||
state?.progress != null &&
|
||||
state!.progress < 100)
|
||||
? 4.0
|
||||
: 0.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return FindInPageWidget(padding: value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (displayedSheet != null)
|
||||
ModalBarrier(
|
||||
color:
|
||||
Theme.of(context).dialogTheme.barrierColor ??
|
||||
Colors.black54,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: ReaderAppearanceButton(),
|
||||
bottomSheet: (displayedSheet != null)
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/search_tab.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/summarize_tab.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/presentation/hooks/sync_page_tab.dart';
|
||||
|
||||
+214
-161
@@ -3,24 +3,74 @@ import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/providers.dart';
|
||||
import 'package:lensai/data/models/drag_data.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/tab_action.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
||||
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:reorderable_grid/reorderable_grid.dart';
|
||||
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
||||
|
||||
class _TabDraggable extends HookConsumerWidget {
|
||||
final String tabId;
|
||||
final String? activeTabId;
|
||||
|
||||
final void Function() onClose;
|
||||
|
||||
_TabDraggable({
|
||||
required this.tabId,
|
||||
required this.activeTabId,
|
||||
required this.onClose,
|
||||
}) : super(key: ValueKey(tabId));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tab = ref.watch(tabStateProvider(tabId));
|
||||
|
||||
if (tab == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return TabPreview(
|
||||
tab: tab,
|
||||
isActive: tabId == activeTabId,
|
||||
onTap: () async {
|
||||
if (tabId != activeTabId) {
|
||||
//Close first to avoid rebuilds
|
||||
onClose();
|
||||
await ref.read(tabRepositoryProvider.notifier).selectTab(tab.id);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onDoubleTap: () {
|
||||
ref.read(overlayDialogControllerProvider.notifier).show(
|
||||
TabActionDialog(
|
||||
initialTab: tab,
|
||||
onDismiss:
|
||||
ref.read(overlayDialogControllerProvider.notifier).dismiss,
|
||||
),
|
||||
);
|
||||
},
|
||||
onDelete: () async {
|
||||
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tab extends HookConsumerWidget {
|
||||
static const headerSize = 124.0;
|
||||
@@ -194,133 +244,164 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
child: _Tab(onClose: onClose),
|
||||
),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
controller: sheetScrollController,
|
||||
slivers: [
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
child: HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
|
||||
final filteredTabs = ref
|
||||
.watch(
|
||||
seamlessFilteredTabsProvider(container).select(
|
||||
(value) =>
|
||||
EquatableCollection(value, immutable: true),
|
||||
final filteredTabs = ref
|
||||
.watch(
|
||||
seamlessFilteredTabsProvider(container).select(
|
||||
(value) =>
|
||||
EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
final activeTab = ref.watch(selectedTabProvider);
|
||||
|
||||
final itemHeight = useMemoized(
|
||||
() => _calculateItemHeight(
|
||||
screenWidth: MediaQuery.of(context).size.width,
|
||||
childAspectRatio: 0.75,
|
||||
horizontalPadding: 4.0,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
[MediaQuery.of(context).size.width],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final index = filteredTabs
|
||||
.indexWhere((webView) => webView == activeTab);
|
||||
|
||||
if (index > -1) {
|
||||
final offset = (index ~/ 2) * itemHeight;
|
||||
|
||||
if (offset != sheetScrollController.offset) {
|
||||
unawaited(
|
||||
sheetScrollController.animateTo(
|
||||
offset,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[filteredTabs, activeTab],
|
||||
);
|
||||
|
||||
final tabs = useMemoized(
|
||||
() {
|
||||
return filteredTabs
|
||||
.mapIndexed(
|
||||
(index, tabId) => CustomDraggable(
|
||||
key: Key(tabId),
|
||||
data: TabDragData(tabId),
|
||||
child: Consumer(
|
||||
child: _TabDraggable(
|
||||
tabId: tabId,
|
||||
activeTabId: activeTab,
|
||||
onClose: onClose,
|
||||
),
|
||||
builder: (context, ref, child) {
|
||||
final dragData = ref.watch(
|
||||
willAcceptDropProvider.select((value) {
|
||||
final dragTabId = switch (value) {
|
||||
ContainerDropData() => value.tabId,
|
||||
DeleteDropData() => value.tabId,
|
||||
null => null,
|
||||
};
|
||||
|
||||
return (dragTabId == tabId)
|
||||
? value
|
||||
: null;
|
||||
}),
|
||||
);
|
||||
|
||||
return switch (dragData) {
|
||||
ContainerDropData() => Opacity(
|
||||
opacity: 0.3,
|
||||
child: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
DeleteDropData() => Opacity(
|
||||
opacity: 0.3,
|
||||
child: ColorFiltered(
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Colors.red,
|
||||
BlendMode.modulate,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
null => child!,
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
.toList();
|
||||
},
|
||||
[
|
||||
EquatableCollection(filteredTabs, immutable: true),
|
||||
activeTab,
|
||||
],
|
||||
);
|
||||
|
||||
final activeTab = ref.watch(selectedTabProvider);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: ReorderableBuilder(
|
||||
scrollController: sheetScrollController,
|
||||
children: tabs,
|
||||
onDragStarted: (index) {
|
||||
ref.read(willAcceptDropProvider.notifier).clear();
|
||||
},
|
||||
onReorderPositions: (positions) async {
|
||||
assert(
|
||||
positions.length == 1,
|
||||
'Not ready for multiple reorders',
|
||||
);
|
||||
|
||||
final itemHeight = useMemoized(
|
||||
() => _calculateItemHeight(
|
||||
screenWidth: MediaQuery.of(context).size.width,
|
||||
childAspectRatio: 0.75,
|
||||
horizontalPadding: 4.0,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
[MediaQuery.of(context).size.width],
|
||||
);
|
||||
final oldIndex = positions.first.oldIndex;
|
||||
final newIndex = positions.first.newIndex;
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final index = filteredTabs
|
||||
.indexWhere((webView) => webView == activeTab);
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
if (index > -1) {
|
||||
final offset = (index ~/ 2) * itemHeight;
|
||||
final tabId = filteredTabs[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
|
||||
if (offset != sheetScrollController.offset) {
|
||||
unawaited(
|
||||
sheetScrollController.animateTo(
|
||||
offset,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
final String key;
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= filteredTabs.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex;
|
||||
key = await containerRepository.getOrderKeyAfterTab(
|
||||
filteredTabs[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[filteredTabs, activeTab],
|
||||
);
|
||||
|
||||
final tabs = useMemoized(
|
||||
() {
|
||||
return filteredTabs
|
||||
.mapIndexed(
|
||||
(index, tabId) =>
|
||||
ReorderableGridDelayedDragStartListener(
|
||||
key: ValueKey(tabId),
|
||||
index: index,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final tab =
|
||||
ref.watch(tabStateProvider(tabId));
|
||||
return (tab != null)
|
||||
? TabPreview(
|
||||
tab: tab,
|
||||
isActive: tabId == activeTab,
|
||||
onTap: () async {
|
||||
if (tabId != activeTab) {
|
||||
//Close first to avoid rebuilds
|
||||
onClose();
|
||||
await ref
|
||||
.read(
|
||||
tabRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.selectTab(tab.id);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onDoubleTap: () {
|
||||
ref
|
||||
.read(
|
||||
overlayDialogControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.show(
|
||||
TabActionDialog(
|
||||
initialTab: tab,
|
||||
onDismiss: ref
|
||||
.read(
|
||||
overlayDialogControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.dismiss,
|
||||
),
|
||||
);
|
||||
},
|
||||
onDelete: () async {
|
||||
await ref
|
||||
.read(
|
||||
tabRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.closeTab(tab.id);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
[
|
||||
EquatableCollection(filteredTabs, immutable: true),
|
||||
activeTab,
|
||||
],
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
sliver: SliverReorderableGrid(
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignOrderKey(tabId, key);
|
||||
},
|
||||
builder: (children) {
|
||||
return GridView.builder(
|
||||
controller: sheetScrollController,
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
//Sync values for itemHeight calculation _calculateItemHeight
|
||||
@@ -329,42 +410,13 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
itemCount: tabs.length,
|
||||
itemBuilder: (context, index) => tabs[index],
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
final tabId = filteredTabs[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
|
||||
final String key;
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= filteredTabs.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex;
|
||||
key =
|
||||
await containerRepository.getOrderKeyAfterTab(
|
||||
filteredTabs[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignOrderKey(tabId, key);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
itemCount: children.length,
|
||||
itemBuilder: (context, index) => children[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -376,13 +428,14 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
),
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: Uri.https('kagi.com'));
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(
|
||||
url: Uri.https('setcookie.net'),
|
||||
// contextId: uuid.v4(),
|
||||
);
|
||||
|
||||
onClose();
|
||||
},
|
||||
child: Icon(Icons.add),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'preference_setting.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
class PreferenceSettingGroup with FastEquatable {
|
||||
final String? description;
|
||||
|
||||
final Map<String, PreferenceSetting> settings;
|
||||
|
||||
bool get isActive => settings.values
|
||||
.every((setting) => setting.requireUserOptIn || setting.isActive);
|
||||
|
||||
bool get isPartlyActive => settings.values.any((setting) => setting.isActive);
|
||||
|
||||
PreferenceSettingGroup({
|
||||
required this.description,
|
||||
required this.settings,
|
||||
});
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
description,
|
||||
settings,
|
||||
];
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
@CopyWith()
|
||||
class PreferenceSetting with FastEquatable {
|
||||
final Object value;
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final Object? actualValue;
|
||||
|
||||
bool get isActive => value == actualValue;
|
||||
|
||||
final String title;
|
||||
final String? description;
|
||||
|
||||
final bool requireUserOptIn;
|
||||
final bool shouldBeDefault;
|
||||
|
||||
PreferenceSetting({
|
||||
required this.value,
|
||||
required this.title,
|
||||
required this.description,
|
||||
this.actualValue,
|
||||
this.requireUserOptIn = false,
|
||||
this.shouldBeDefault = false,
|
||||
});
|
||||
|
||||
factory PreferenceSetting.fromJson(Map<String, dynamic> json) =>
|
||||
_$PreferenceSettingFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$PreferenceSettingToJson(this);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
value,
|
||||
actualValue,
|
||||
title,
|
||||
description,
|
||||
requireUserOptIn,
|
||||
shouldBeDefault,
|
||||
];
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'preference_setting.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$PreferenceSettingGroupCWProxy {
|
||||
PreferenceSettingGroup description(String? description);
|
||||
|
||||
PreferenceSettingGroup settings(Map<String, PreferenceSetting> settings);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PreferenceSettingGroup(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// PreferenceSettingGroup(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
PreferenceSettingGroup call({
|
||||
String? description,
|
||||
Map<String, PreferenceSetting> settings,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfPreferenceSettingGroup.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfPreferenceSettingGroup.copyWith.fieldName(...)`
|
||||
class _$PreferenceSettingGroupCWProxyImpl
|
||||
implements _$PreferenceSettingGroupCWProxy {
|
||||
const _$PreferenceSettingGroupCWProxyImpl(this._value);
|
||||
|
||||
final PreferenceSettingGroup _value;
|
||||
|
||||
@override
|
||||
PreferenceSettingGroup description(String? description) =>
|
||||
this(description: description);
|
||||
|
||||
@override
|
||||
PreferenceSettingGroup settings(Map<String, PreferenceSetting> settings) =>
|
||||
this(settings: settings);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PreferenceSettingGroup(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// PreferenceSettingGroup(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
PreferenceSettingGroup call({
|
||||
Object? description = const $CopyWithPlaceholder(),
|
||||
Object? settings = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return PreferenceSettingGroup(
|
||||
description: description == const $CopyWithPlaceholder()
|
||||
? _value.description
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: description as String?,
|
||||
settings: settings == const $CopyWithPlaceholder()
|
||||
? _value.settings
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: settings as Map<String, PreferenceSetting>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $PreferenceSettingGroupCopyWith on PreferenceSettingGroup {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfPreferenceSettingGroup.copyWith(...)` or like so:`instanceOfPreferenceSettingGroup.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$PreferenceSettingGroupCWProxy get copyWith =>
|
||||
_$PreferenceSettingGroupCWProxyImpl(this);
|
||||
}
|
||||
|
||||
abstract class _$PreferenceSettingCWProxy {
|
||||
PreferenceSetting value(Object value);
|
||||
|
||||
PreferenceSetting title(String title);
|
||||
|
||||
PreferenceSetting description(String? description);
|
||||
|
||||
PreferenceSetting actualValue(Object? actualValue);
|
||||
|
||||
PreferenceSetting requireUserOptIn(bool requireUserOptIn);
|
||||
|
||||
PreferenceSetting shouldBeDefault(bool shouldBeDefault);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PreferenceSetting(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// PreferenceSetting(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
PreferenceSetting call({
|
||||
Object value,
|
||||
String title,
|
||||
String? description,
|
||||
Object? actualValue,
|
||||
bool requireUserOptIn,
|
||||
bool shouldBeDefault,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfPreferenceSetting.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfPreferenceSetting.copyWith.fieldName(...)`
|
||||
class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
|
||||
const _$PreferenceSettingCWProxyImpl(this._value);
|
||||
|
||||
final PreferenceSetting _value;
|
||||
|
||||
@override
|
||||
PreferenceSetting value(Object value) => this(value: value);
|
||||
|
||||
@override
|
||||
PreferenceSetting title(String title) => this(title: title);
|
||||
|
||||
@override
|
||||
PreferenceSetting description(String? description) =>
|
||||
this(description: description);
|
||||
|
||||
@override
|
||||
PreferenceSetting actualValue(Object? actualValue) =>
|
||||
this(actualValue: actualValue);
|
||||
|
||||
@override
|
||||
PreferenceSetting requireUserOptIn(bool requireUserOptIn) =>
|
||||
this(requireUserOptIn: requireUserOptIn);
|
||||
|
||||
@override
|
||||
PreferenceSetting shouldBeDefault(bool shouldBeDefault) =>
|
||||
this(shouldBeDefault: shouldBeDefault);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PreferenceSetting(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// PreferenceSetting(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
PreferenceSetting call({
|
||||
Object? value = const $CopyWithPlaceholder(),
|
||||
Object? title = const $CopyWithPlaceholder(),
|
||||
Object? description = const $CopyWithPlaceholder(),
|
||||
Object? actualValue = const $CopyWithPlaceholder(),
|
||||
Object? requireUserOptIn = const $CopyWithPlaceholder(),
|
||||
Object? shouldBeDefault = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return PreferenceSetting(
|
||||
value: value == const $CopyWithPlaceholder()
|
||||
? _value.value
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: value as Object,
|
||||
title: title == const $CopyWithPlaceholder()
|
||||
? _value.title
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: title as String,
|
||||
description: description == const $CopyWithPlaceholder()
|
||||
? _value.description
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: description as String?,
|
||||
actualValue: actualValue == const $CopyWithPlaceholder()
|
||||
? _value.actualValue
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: actualValue as Object?,
|
||||
requireUserOptIn: requireUserOptIn == const $CopyWithPlaceholder()
|
||||
? _value.requireUserOptIn
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: requireUserOptIn as bool,
|
||||
shouldBeDefault: shouldBeDefault == const $CopyWithPlaceholder()
|
||||
? _value.shouldBeDefault
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: shouldBeDefault as bool,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $PreferenceSettingCopyWith on PreferenceSetting {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfPreferenceSetting.copyWith(...)` or like so:`instanceOfPreferenceSetting.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$PreferenceSettingCWProxy get copyWith =>
|
||||
_$PreferenceSettingCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
PreferenceSetting _$PreferenceSettingFromJson(Map<String, dynamic> json) =>
|
||||
PreferenceSetting(
|
||||
value: json['value'] as Object,
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String?,
|
||||
requireUserOptIn: json['requireUserOptIn'] as bool? ?? false,
|
||||
shouldBeDefault: json['shouldBeDefault'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PreferenceSettingToJson(PreferenceSetting instance) =>
|
||||
<String, dynamic>{
|
||||
'value': instance.value,
|
||||
'title': instance.title,
|
||||
'description': instance.description,
|
||||
'requireUserOptIn': instance.requireUserOptIn,
|
||||
'shouldBeDefault': instance.shouldBeDefault,
|
||||
};
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/geckoview/features/preferences/data/models/preference_setting.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'preference_settings.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<Map<String, PreferenceSettingGroup>> _preferenceSettingGroups(
|
||||
Ref ref,
|
||||
) async {
|
||||
final content = await rootBundle
|
||||
.loadString('assets/preferences/settings.json')
|
||||
.then(jsonDecode) as Map<String, dynamic>;
|
||||
|
||||
final userContent = content['user'] as Map<String, dynamic>;
|
||||
|
||||
return userContent.map(
|
||||
(key, value) => MapEntry(
|
||||
key,
|
||||
PreferenceSettingGroup(
|
||||
// ignore: avoid_dynamic_calls
|
||||
description: value['description'] as String?,
|
||||
// ignore: avoid_dynamic_calls
|
||||
settings: (value['preferences'] as Map<String, dynamic>).map(
|
||||
(key, value) => MapEntry(
|
||||
key,
|
||||
PreferenceSetting.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PreferenceSettingGroup> _preferenceSettingGroup(
|
||||
Ref ref,
|
||||
String groupName,
|
||||
) {
|
||||
return ref.watch(
|
||||
_preferenceSettingGroupsProvider.selectAsync(
|
||||
(groups) =>
|
||||
groups[groupName] ?? (throw Exception('Unknown setting group')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
class _PreferenceRepository extends _$PreferenceRepository {
|
||||
final _prefManager = GeckoPrefService();
|
||||
// Use BehaviorSubject instead of StreamController
|
||||
late BehaviorSubject<Map<String, Object>> _prefSubject;
|
||||
|
||||
Future<void> _updatePrefs() async {
|
||||
final prefs = await _prefManager.getAllPrefs();
|
||||
_prefSubject.add(prefs);
|
||||
}
|
||||
|
||||
Future<void> applyPrefs(Map<String, Object> prefs) async {
|
||||
await _prefManager.applyPrefs(prefs);
|
||||
await _updatePrefs();
|
||||
}
|
||||
|
||||
Future<void> resetPrefs(List<String> prefNames) async {
|
||||
await _prefManager.resetPrefs(prefNames);
|
||||
await _updatePrefs();
|
||||
}
|
||||
|
||||
@override
|
||||
Raw<Stream<Map<String, Object>>> build() {
|
||||
_prefSubject = BehaviorSubject<Map<String, Object>>();
|
||||
|
||||
ref.onDispose(() {
|
||||
unawaited(_prefSubject.close());
|
||||
});
|
||||
|
||||
ref.onAddListener(() async {
|
||||
await _updatePrefs();
|
||||
});
|
||||
|
||||
return _prefSubject.stream;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
class PreferenceSettingsGeneralRepository
|
||||
extends _$PreferenceSettingsGeneralRepository {
|
||||
late Map<String, PreferenceSettingGroup> _statelessGroups;
|
||||
|
||||
Future<void> apply() async {
|
||||
final prefs = {
|
||||
for (final group in _statelessGroups.values)
|
||||
...Map.fromEntries(
|
||||
group.settings.entries
|
||||
.where((e) => !e.value.requireUserOptIn)
|
||||
.map((e) => MapEntry(e.key, e.value.value)),
|
||||
),
|
||||
};
|
||||
|
||||
await ref.read(_preferenceRepositoryProvider.notifier).applyPrefs(prefs);
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
final prefs = _statelessGroups.values
|
||||
.map((group) => group.settings.keys)
|
||||
.flattened
|
||||
.toList();
|
||||
|
||||
await ref.read(_preferenceRepositoryProvider.notifier).resetPrefs(prefs);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Map<String, PreferenceSettingGroup>> build() async* {
|
||||
_statelessGroups = await ref.watch(_preferenceSettingGroupsProvider.future);
|
||||
|
||||
final prefStream = ref.watch(_preferenceRepositoryProvider);
|
||||
|
||||
yield* prefStream.map(
|
||||
(prefs) => _statelessGroups.map(
|
||||
(groupName, group) => MapEntry(
|
||||
groupName,
|
||||
group.copyWith.settings(
|
||||
group.settings.map(
|
||||
(prefName, setting) => MapEntry(
|
||||
prefName,
|
||||
setting.copyWith.actualValue(prefs[prefName]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
class PreferenceSettingsGroupRepository
|
||||
extends _$PreferenceSettingsGroupRepository {
|
||||
late PreferenceSettingGroup _statelessSettingGroup;
|
||||
|
||||
Future<void> apply({List<String>? filter}) async {
|
||||
final prefs = Map.fromEntries(
|
||||
filter?.map(
|
||||
(e) => MapEntry(
|
||||
e,
|
||||
_statelessSettingGroup.settings[e]?.value ??
|
||||
(throw Exception('Preference not part of group')),
|
||||
),
|
||||
) ??
|
||||
_statelessSettingGroup.settings.entries
|
||||
.where((e) => !e.value.requireUserOptIn)
|
||||
.map((e) => MapEntry(e.key, e.value.value)),
|
||||
);
|
||||
|
||||
await ref.read(_preferenceRepositoryProvider.notifier).applyPrefs(prefs);
|
||||
}
|
||||
|
||||
Future<void> reset({List<String>? filter}) async {
|
||||
if (filter != null &&
|
||||
filter.any(
|
||||
(value) => !_statelessSettingGroup.settings.containsKey(value),
|
||||
)) {
|
||||
throw Exception('Preference not part of group');
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(_preferenceRepositoryProvider.notifier)
|
||||
.resetPrefs(filter ?? _statelessSettingGroup.settings.keys.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<PreferenceSettingGroup> build(String groupName) async* {
|
||||
_statelessSettingGroup =
|
||||
await ref.watch(_preferenceSettingGroupProvider(groupName).future);
|
||||
|
||||
final prefStream = ref.watch(_preferenceRepositoryProvider);
|
||||
|
||||
yield* prefStream.map(
|
||||
(prefs) => _statelessSettingGroup.copyWith.settings(
|
||||
_statelessSettingGroup.settings.map(
|
||||
(key, value) => MapEntry(key, value.copyWith.actualValue(prefs[key])),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'preference_settings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$preferenceSettingGroupsHash() =>
|
||||
r'fa9286248cc71c88c888a518441721dc99b7e0c2';
|
||||
|
||||
/// See also [_preferenceSettingGroups].
|
||||
@ProviderFor(_preferenceSettingGroups)
|
||||
final _preferenceSettingGroupsProvider =
|
||||
FutureProvider<Map<String, PreferenceSettingGroup>>.internal(
|
||||
_preferenceSettingGroups,
|
||||
name: r'_preferenceSettingGroupsProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceSettingGroupsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef _PreferenceSettingGroupsRef
|
||||
= FutureProviderRef<Map<String, PreferenceSettingGroup>>;
|
||||
String _$preferenceSettingGroupHash() =>
|
||||
r'960169ab9f6985a5b73a63663e671a8eac7cd776';
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [_preferenceSettingGroup].
|
||||
@ProviderFor(_preferenceSettingGroup)
|
||||
const _preferenceSettingGroupProvider = _PreferenceSettingGroupFamily();
|
||||
|
||||
/// See also [_preferenceSettingGroup].
|
||||
class _PreferenceSettingGroupFamily
|
||||
extends Family<AsyncValue<PreferenceSettingGroup>> {
|
||||
/// See also [_preferenceSettingGroup].
|
||||
const _PreferenceSettingGroupFamily();
|
||||
|
||||
/// See also [_preferenceSettingGroup].
|
||||
_PreferenceSettingGroupProvider call(
|
||||
String groupName,
|
||||
) {
|
||||
return _PreferenceSettingGroupProvider(
|
||||
groupName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
_PreferenceSettingGroupProvider getProviderOverride(
|
||||
covariant _PreferenceSettingGroupProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.groupName,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'_preferenceSettingGroupProvider';
|
||||
}
|
||||
|
||||
/// See also [_preferenceSettingGroup].
|
||||
class _PreferenceSettingGroupProvider
|
||||
extends FutureProvider<PreferenceSettingGroup> {
|
||||
/// See also [_preferenceSettingGroup].
|
||||
_PreferenceSettingGroupProvider(
|
||||
String groupName,
|
||||
) : this._internal(
|
||||
(ref) => _preferenceSettingGroup(
|
||||
ref as _PreferenceSettingGroupRef,
|
||||
groupName,
|
||||
),
|
||||
from: _preferenceSettingGroupProvider,
|
||||
name: r'_preferenceSettingGroupProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceSettingGroupHash,
|
||||
dependencies: _PreferenceSettingGroupFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
_PreferenceSettingGroupFamily._allTransitiveDependencies,
|
||||
groupName: groupName,
|
||||
);
|
||||
|
||||
_PreferenceSettingGroupProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.groupName,
|
||||
}) : super.internal();
|
||||
|
||||
final String groupName;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<PreferenceSettingGroup> Function(
|
||||
_PreferenceSettingGroupRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: _PreferenceSettingGroupProvider._internal(
|
||||
(ref) => create(ref as _PreferenceSettingGroupRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
groupName: groupName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureProviderElement<PreferenceSettingGroup> createElement() {
|
||||
return _PreferenceSettingGroupProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is _PreferenceSettingGroupProvider &&
|
||||
other.groupName == groupName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, groupName.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin _PreferenceSettingGroupRef on FutureProviderRef<PreferenceSettingGroup> {
|
||||
/// The parameter `groupName` of this provider.
|
||||
String get groupName;
|
||||
}
|
||||
|
||||
class _PreferenceSettingGroupProviderElement
|
||||
extends FutureProviderElement<PreferenceSettingGroup>
|
||||
with _PreferenceSettingGroupRef {
|
||||
_PreferenceSettingGroupProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get groupName => (origin as _PreferenceSettingGroupProvider).groupName;
|
||||
}
|
||||
|
||||
String _$preferenceRepositoryHash() =>
|
||||
r'53c0cbbf60e44c3834fe3e3255836f7e27f09c7f';
|
||||
|
||||
/// See also [_PreferenceRepository].
|
||||
@ProviderFor(_PreferenceRepository)
|
||||
final _preferenceRepositoryProvider = AutoDisposeNotifierProvider<
|
||||
_PreferenceRepository, Raw<Stream<Map<String, Object>>>>.internal(
|
||||
_PreferenceRepository.new,
|
||||
name: r'_preferenceRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$PreferenceRepository
|
||||
= AutoDisposeNotifier<Raw<Stream<Map<String, Object>>>>;
|
||||
String _$preferenceSettingsGeneralRepositoryHash() =>
|
||||
r'24a70de8284107bb43778e1014abe41bd805987a';
|
||||
|
||||
/// See also [PreferenceSettingsGeneralRepository].
|
||||
@ProviderFor(PreferenceSettingsGeneralRepository)
|
||||
final preferenceSettingsGeneralRepositoryProvider =
|
||||
AutoDisposeStreamNotifierProvider<PreferenceSettingsGeneralRepository,
|
||||
Map<String, PreferenceSettingGroup>>.internal(
|
||||
PreferenceSettingsGeneralRepository.new,
|
||||
name: r'preferenceSettingsGeneralRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceSettingsGeneralRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$PreferenceSettingsGeneralRepository
|
||||
= AutoDisposeStreamNotifier<Map<String, PreferenceSettingGroup>>;
|
||||
String _$preferenceSettingsGroupRepositoryHash() =>
|
||||
r'023794294c5639ac6c6941f87a4ed91e1551fbc9';
|
||||
|
||||
abstract class _$PreferenceSettingsGroupRepository
|
||||
extends BuildlessAutoDisposeStreamNotifier<PreferenceSettingGroup> {
|
||||
late final String groupName;
|
||||
|
||||
Stream<PreferenceSettingGroup> build(
|
||||
String groupName,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
@ProviderFor(PreferenceSettingsGroupRepository)
|
||||
const preferenceSettingsGroupRepositoryProvider =
|
||||
PreferenceSettingsGroupRepositoryFamily();
|
||||
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
class PreferenceSettingsGroupRepositoryFamily
|
||||
extends Family<AsyncValue<PreferenceSettingGroup>> {
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
const PreferenceSettingsGroupRepositoryFamily();
|
||||
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
PreferenceSettingsGroupRepositoryProvider call(
|
||||
String groupName,
|
||||
) {
|
||||
return PreferenceSettingsGroupRepositoryProvider(
|
||||
groupName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PreferenceSettingsGroupRepositoryProvider getProviderOverride(
|
||||
covariant PreferenceSettingsGroupRepositoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.groupName,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'preferenceSettingsGroupRepositoryProvider';
|
||||
}
|
||||
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
class PreferenceSettingsGroupRepositoryProvider
|
||||
extends AutoDisposeStreamNotifierProviderImpl<
|
||||
PreferenceSettingsGroupRepository, PreferenceSettingGroup> {
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
PreferenceSettingsGroupRepositoryProvider(
|
||||
String groupName,
|
||||
) : this._internal(
|
||||
() => PreferenceSettingsGroupRepository()..groupName = groupName,
|
||||
from: preferenceSettingsGroupRepositoryProvider,
|
||||
name: r'preferenceSettingsGroupRepositoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceSettingsGroupRepositoryHash,
|
||||
dependencies: PreferenceSettingsGroupRepositoryFamily._dependencies,
|
||||
allTransitiveDependencies: PreferenceSettingsGroupRepositoryFamily
|
||||
._allTransitiveDependencies,
|
||||
groupName: groupName,
|
||||
);
|
||||
|
||||
PreferenceSettingsGroupRepositoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.groupName,
|
||||
}) : super.internal();
|
||||
|
||||
final String groupName;
|
||||
|
||||
@override
|
||||
Stream<PreferenceSettingGroup> runNotifierBuild(
|
||||
covariant PreferenceSettingsGroupRepository notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
groupName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(PreferenceSettingsGroupRepository Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PreferenceSettingsGroupRepositoryProvider._internal(
|
||||
() => create()..groupName = groupName,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
groupName: groupName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamNotifierProviderElement<PreferenceSettingsGroupRepository,
|
||||
PreferenceSettingGroup> createElement() {
|
||||
return _PreferenceSettingsGroupRepositoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PreferenceSettingsGroupRepositoryProvider &&
|
||||
other.groupName == groupName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, groupName.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PreferenceSettingsGroupRepositoryRef
|
||||
on AutoDisposeStreamNotifierProviderRef<PreferenceSettingGroup> {
|
||||
/// The parameter `groupName` of this provider.
|
||||
String get groupName;
|
||||
}
|
||||
|
||||
class _PreferenceSettingsGroupRepositoryProviderElement
|
||||
extends AutoDisposeStreamNotifierProviderElement<
|
||||
PreferenceSettingsGroupRepository,
|
||||
PreferenceSettingGroup> with PreferenceSettingsGroupRepositoryRef {
|
||||
_PreferenceSettingsGroupRepositoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get groupName =>
|
||||
(origin as PreferenceSettingsGroupRepositoryProvider).groupName;
|
||||
}
|
||||
// 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
|
||||
@@ -9,9 +9,7 @@ import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_re
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart';
|
||||
import 'package:lensai/features/search/domain/entities/abstract/i_query_builder.dart';
|
||||
import 'package:lensai/features/search/domain/fts_tokenizer.dart';
|
||||
import 'package:lensai/features/search/domain/unix_tokenizer.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/providers.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/data/models/drag_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
|
||||
class ContainerChips extends HookConsumerWidget {
|
||||
@@ -34,6 +38,8 @@ class ContainerChips extends HookConsumerWidget {
|
||||
final containersAsync =
|
||||
ref.watch(filteredContainersWithCountProvider(searchText));
|
||||
|
||||
final dragTargetTabId = useValueNotifier<String?>(null);
|
||||
|
||||
return containersAsync.when(
|
||||
data: (availableContainers) {
|
||||
if (selectedContainer == null &&
|
||||
@@ -62,6 +68,74 @@ class ContainerChips extends HookConsumerWidget {
|
||||
itemLabel: (container) =>
|
||||
Text(container.name ?? 'New Container'),
|
||||
itemBadgeCount: (container) => container.tabCount,
|
||||
itemWrap: (child, container) {
|
||||
return HookBuilder(
|
||||
builder: (context) {
|
||||
final overlayController =
|
||||
useOverlayPortalController();
|
||||
|
||||
return DragTarget<TabDragData>(
|
||||
onMove: (details) {
|
||||
ref.read(willAcceptDropProvider.notifier).setData(
|
||||
ContainerDropData(details.data.tabId),
|
||||
);
|
||||
|
||||
dragTargetTabId.value = details.data.tabId;
|
||||
overlayController.show();
|
||||
},
|
||||
onLeave: (data) {
|
||||
ref.read(willAcceptDropProvider.notifier).clear();
|
||||
|
||||
dragTargetTabId.value = null;
|
||||
overlayController.hide();
|
||||
},
|
||||
onAcceptWithDetails: (details) async {
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(
|
||||
details.data.tabId,
|
||||
container.id,
|
||||
);
|
||||
|
||||
dragTargetTabId.value = null;
|
||||
overlayController.hide();
|
||||
},
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
final renderBox =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
final position =
|
||||
renderBox?.localToGlobal(Offset.zero) ??
|
||||
Offset.zero;
|
||||
|
||||
return OverlayPortal(
|
||||
controller: overlayController,
|
||||
overlayChildBuilder: (context) => Positioned(
|
||||
top: position.dy,
|
||||
left: position.dx,
|
||||
child: IgnorePointer(
|
||||
child: Transform.scale(
|
||||
scale: 1.1,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: HookBuilder(
|
||||
builder: (context) {
|
||||
final dragTabId =
|
||||
useValueListenable(dragTargetTabId);
|
||||
|
||||
return Opacity(
|
||||
opacity: (dragTabId == null) ? 1.0 : 0.0,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
availableItems: availableContainers,
|
||||
selectedItem: selectedContainer,
|
||||
onSelected: onSelected,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/geckoview/features/preferences/data/repositories/preference_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/hardening_group_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
class BrowserHardeningScreen extends HookConsumerWidget {
|
||||
const BrowserHardeningScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final preferenceGroups =
|
||||
ref.watch(preferenceSettingsGeneralRepositoryProvider);
|
||||
|
||||
final allGroupsActive = useMemoized(
|
||||
() =>
|
||||
preferenceGroups.valueOrNull?.values.every(
|
||||
(element) => element.isActive,
|
||||
) ??
|
||||
false,
|
||||
[EquatableCollection(preferenceGroups.valueOrNull, immutable: true)],
|
||||
);
|
||||
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Browser Hardening')),
|
||||
body: preferenceGroups.when(
|
||||
data: (data) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SwitchListTile(
|
||||
value: allGroupsActive,
|
||||
title: Text(
|
||||
'Complete Hardening',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
preferenceSettingsGeneralRepositoryProvider.notifier,
|
||||
);
|
||||
|
||||
if (value) {
|
||||
await notifier.apply();
|
||||
} else {
|
||||
await notifier.reset();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: data.entries.map(
|
||||
(group) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
title: Text(group.key),
|
||||
subtitle: (group.value.description != null)
|
||||
? Text(group.value.description!)
|
||||
: null,
|
||||
leading: HardeningGroupIcon(
|
||||
isActive: group.value.isActive,
|
||||
isPartlyActive: group.value.isPartlyActive,
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
BrowserHardeningGroupRoute(
|
||||
group: group.key,
|
||||
).location,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) => FailureWidget(
|
||||
title: 'Could not load preference settings',
|
||||
exception: error,
|
||||
onRetry: () =>
|
||||
ref.refresh(preferenceSettingsGeneralRepositoryProvider),
|
||||
),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/features/preferences/data/repositories/preference_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/hardening_group_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
class BrowserHardeningGroupScreen extends HookConsumerWidget {
|
||||
final String groupName;
|
||||
|
||||
const BrowserHardeningGroupScreen({super.key, required this.groupName});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settings =
|
||||
ref.watch(preferenceSettingsGroupRepositoryProvider(groupName));
|
||||
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(groupName)),
|
||||
body: settings.when(
|
||||
data: (group) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SwitchListTile(
|
||||
value: group.isActive,
|
||||
title: Text(
|
||||
groupName,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
subtitle: (group.description != null)
|
||||
? Text(
|
||||
group.description!,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
preferenceSettingsGroupRepositoryProvider(groupName)
|
||||
.notifier,
|
||||
);
|
||||
|
||||
if (value) {
|
||||
await notifier.apply();
|
||||
} else {
|
||||
await notifier.reset();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: group.settings.entries.map(
|
||||
(setting) {
|
||||
return Row(
|
||||
children: [
|
||||
if (!setting.value.shouldBeDefault ||
|
||||
!setting.value.isActive)
|
||||
Expanded(
|
||||
child: SwitchListTile(
|
||||
value: setting.value.isActive,
|
||||
title: Text(setting.value.title),
|
||||
subtitle: (setting.value.description != null)
|
||||
? Text(setting.value.description!)
|
||||
: null,
|
||||
secondary: HardeningGroupIcon(
|
||||
isActive: setting.value.isActive,
|
||||
),
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
preferenceSettingsGroupRepositoryProvider(
|
||||
groupName,
|
||||
).notifier,
|
||||
);
|
||||
|
||||
if (value) {
|
||||
await notifier.apply(filter: [setting.key]);
|
||||
} else {
|
||||
await notifier.reset(filter: [setting.key]);
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
title: Text(setting.value.title),
|
||||
subtitle: (setting.value.description != null)
|
||||
? Text(setting.value.description!)
|
||||
: null,
|
||||
leading: HardeningGroupIcon(
|
||||
isActive: setting.value.isActive,
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.only(right: 18.0),
|
||||
child: const Icon(Icons.check),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) => FailureWidget(
|
||||
title: 'Could not load preference settings',
|
||||
exception: error,
|
||||
onRetry: () =>
|
||||
ref.refresh(preferenceSettingsGroupRepositoryProvider(groupName)),
|
||||
),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||
@@ -50,6 +52,17 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
controller: controller,
|
||||
children: [
|
||||
_buildSection(theme, 'General'),
|
||||
Card(
|
||||
child: ListTile(
|
||||
title: const Text('Browser Hardening'),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
await context.push(BrowserHardeningRoute().location);
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
|
||||
class HardeningGroupIcon extends StatelessWidget {
|
||||
final bool isActive;
|
||||
final bool isPartlyActive;
|
||||
|
||||
const HardeningGroupIcon({
|
||||
super.key,
|
||||
required this.isActive,
|
||||
this.isPartlyActive = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isActive) {
|
||||
return Icon(
|
||||
MdiIcons.shieldLockOutline,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
);
|
||||
} else if (isPartlyActive) {
|
||||
return Icon(
|
||||
MdiIcons.shieldLockOpenOutline,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
);
|
||||
}
|
||||
|
||||
return Icon(
|
||||
MdiIcons.shieldOffOutline,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'engine_settings.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class EngineSettings extends GeckoEngineSettings {
|
||||
EngineSettings({
|
||||
super.javascriptEnabled,
|
||||
super.trackingProtectionPolicy,
|
||||
super.httpsOnlyMode,
|
||||
super.globalPrivacyControlEnabled,
|
||||
super.preferredColorScheme,
|
||||
super.cookieBannerHandlingMode,
|
||||
super.cookieBannerHandlingModePrivateBrowsing,
|
||||
super.cookieBannerHandlingGlobalRules,
|
||||
super.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
super.webContentIsolationStrategy,
|
||||
});
|
||||
|
||||
EngineSettings.withDefaults({
|
||||
bool? javascriptEnabled,
|
||||
TrackingProtectionPolicy? trackingProtectionPolicy,
|
||||
HttpsOnlyMode? httpsOnlyMode,
|
||||
bool? globalPrivacyControlEnabled,
|
||||
ColorScheme? preferredColorScheme,
|
||||
CookieBannerHandlingMode? cookieBannerHandlingMode,
|
||||
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing,
|
||||
bool? cookieBannerHandlingGlobalRules,
|
||||
bool? cookieBannerHandlingGlobalRulesSubFrames,
|
||||
WebContentIsolationStrategy? webContentIsolationStrategy,
|
||||
}) : super(
|
||||
javascriptEnabled: javascriptEnabled ?? true,
|
||||
trackingProtectionPolicy:
|
||||
trackingProtectionPolicy ?? TrackingProtectionPolicy.strict,
|
||||
httpsOnlyMode: httpsOnlyMode ?? HttpsOnlyMode.enabled,
|
||||
globalPrivacyControlEnabled: globalPrivacyControlEnabled ?? true,
|
||||
preferredColorScheme: preferredColorScheme ?? ColorScheme.system,
|
||||
cookieBannerHandlingMode:
|
||||
cookieBannerHandlingMode ?? CookieBannerHandlingMode.rejectAll,
|
||||
cookieBannerHandlingModePrivateBrowsing:
|
||||
cookieBannerHandlingModePrivateBrowsing ??
|
||||
CookieBannerHandlingMode.rejectAll,
|
||||
cookieBannerHandlingGlobalRules:
|
||||
cookieBannerHandlingGlobalRules ?? true,
|
||||
cookieBannerHandlingGlobalRulesSubFrames:
|
||||
cookieBannerHandlingGlobalRulesSubFrames ?? true,
|
||||
webContentIsolationStrategy: webContentIsolationStrategy ??
|
||||
WebContentIsolationStrategy.isolateHighValue,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'engine_settings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$EngineSettingsCWProxy {
|
||||
EngineSettings javascriptEnabled(bool? javascriptEnabled);
|
||||
|
||||
EngineSettings trackingProtectionPolicy(
|
||||
TrackingProtectionPolicy? trackingProtectionPolicy);
|
||||
|
||||
EngineSettings httpsOnlyMode(HttpsOnlyMode? httpsOnlyMode);
|
||||
|
||||
EngineSettings globalPrivacyControlEnabled(bool? globalPrivacyControlEnabled);
|
||||
|
||||
EngineSettings preferredColorScheme(ColorScheme? preferredColorScheme);
|
||||
|
||||
EngineSettings cookieBannerHandlingMode(
|
||||
CookieBannerHandlingMode? cookieBannerHandlingMode);
|
||||
|
||||
EngineSettings cookieBannerHandlingModePrivateBrowsing(
|
||||
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing);
|
||||
|
||||
EngineSettings cookieBannerHandlingGlobalRules(
|
||||
bool? cookieBannerHandlingGlobalRules);
|
||||
|
||||
EngineSettings cookieBannerHandlingGlobalRulesSubFrames(
|
||||
bool? cookieBannerHandlingGlobalRulesSubFrames);
|
||||
|
||||
EngineSettings webContentIsolationStrategy(
|
||||
WebContentIsolationStrategy? webContentIsolationStrategy);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `EngineSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// EngineSettings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
EngineSettings call({
|
||||
bool? javascriptEnabled,
|
||||
TrackingProtectionPolicy? trackingProtectionPolicy,
|
||||
HttpsOnlyMode? httpsOnlyMode,
|
||||
bool? globalPrivacyControlEnabled,
|
||||
ColorScheme? preferredColorScheme,
|
||||
CookieBannerHandlingMode? cookieBannerHandlingMode,
|
||||
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing,
|
||||
bool? cookieBannerHandlingGlobalRules,
|
||||
bool? cookieBannerHandlingGlobalRulesSubFrames,
|
||||
WebContentIsolationStrategy? webContentIsolationStrategy,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfEngineSettings.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfEngineSettings.copyWith.fieldName(...)`
|
||||
class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
|
||||
const _$EngineSettingsCWProxyImpl(this._value);
|
||||
|
||||
final EngineSettings _value;
|
||||
|
||||
@override
|
||||
EngineSettings javascriptEnabled(bool? javascriptEnabled) =>
|
||||
this(javascriptEnabled: javascriptEnabled);
|
||||
|
||||
@override
|
||||
EngineSettings trackingProtectionPolicy(
|
||||
TrackingProtectionPolicy? trackingProtectionPolicy) =>
|
||||
this(trackingProtectionPolicy: trackingProtectionPolicy);
|
||||
|
||||
@override
|
||||
EngineSettings httpsOnlyMode(HttpsOnlyMode? httpsOnlyMode) =>
|
||||
this(httpsOnlyMode: httpsOnlyMode);
|
||||
|
||||
@override
|
||||
EngineSettings globalPrivacyControlEnabled(
|
||||
bool? globalPrivacyControlEnabled) =>
|
||||
this(globalPrivacyControlEnabled: globalPrivacyControlEnabled);
|
||||
|
||||
@override
|
||||
EngineSettings preferredColorScheme(ColorScheme? preferredColorScheme) =>
|
||||
this(preferredColorScheme: preferredColorScheme);
|
||||
|
||||
@override
|
||||
EngineSettings cookieBannerHandlingMode(
|
||||
CookieBannerHandlingMode? cookieBannerHandlingMode) =>
|
||||
this(cookieBannerHandlingMode: cookieBannerHandlingMode);
|
||||
|
||||
@override
|
||||
EngineSettings cookieBannerHandlingModePrivateBrowsing(
|
||||
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing) =>
|
||||
this(
|
||||
cookieBannerHandlingModePrivateBrowsing:
|
||||
cookieBannerHandlingModePrivateBrowsing);
|
||||
|
||||
@override
|
||||
EngineSettings cookieBannerHandlingGlobalRules(
|
||||
bool? cookieBannerHandlingGlobalRules) =>
|
||||
this(cookieBannerHandlingGlobalRules: cookieBannerHandlingGlobalRules);
|
||||
|
||||
@override
|
||||
EngineSettings cookieBannerHandlingGlobalRulesSubFrames(
|
||||
bool? cookieBannerHandlingGlobalRulesSubFrames) =>
|
||||
this(
|
||||
cookieBannerHandlingGlobalRulesSubFrames:
|
||||
cookieBannerHandlingGlobalRulesSubFrames);
|
||||
|
||||
@override
|
||||
EngineSettings webContentIsolationStrategy(
|
||||
WebContentIsolationStrategy? webContentIsolationStrategy) =>
|
||||
this(webContentIsolationStrategy: webContentIsolationStrategy);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `EngineSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// EngineSettings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
EngineSettings call({
|
||||
Object? javascriptEnabled = const $CopyWithPlaceholder(),
|
||||
Object? trackingProtectionPolicy = const $CopyWithPlaceholder(),
|
||||
Object? httpsOnlyMode = const $CopyWithPlaceholder(),
|
||||
Object? globalPrivacyControlEnabled = const $CopyWithPlaceholder(),
|
||||
Object? preferredColorScheme = const $CopyWithPlaceholder(),
|
||||
Object? cookieBannerHandlingMode = const $CopyWithPlaceholder(),
|
||||
Object? cookieBannerHandlingModePrivateBrowsing =
|
||||
const $CopyWithPlaceholder(),
|
||||
Object? cookieBannerHandlingGlobalRules = const $CopyWithPlaceholder(),
|
||||
Object? cookieBannerHandlingGlobalRulesSubFrames =
|
||||
const $CopyWithPlaceholder(),
|
||||
Object? webContentIsolationStrategy = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return EngineSettings(
|
||||
javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder()
|
||||
? _value.javascriptEnabled
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: javascriptEnabled as bool?,
|
||||
trackingProtectionPolicy:
|
||||
trackingProtectionPolicy == const $CopyWithPlaceholder()
|
||||
? _value.trackingProtectionPolicy
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: trackingProtectionPolicy as TrackingProtectionPolicy?,
|
||||
httpsOnlyMode: httpsOnlyMode == const $CopyWithPlaceholder()
|
||||
? _value.httpsOnlyMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: httpsOnlyMode as HttpsOnlyMode?,
|
||||
globalPrivacyControlEnabled:
|
||||
globalPrivacyControlEnabled == const $CopyWithPlaceholder()
|
||||
? _value.globalPrivacyControlEnabled
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: globalPrivacyControlEnabled as bool?,
|
||||
preferredColorScheme: preferredColorScheme == const $CopyWithPlaceholder()
|
||||
? _value.preferredColorScheme
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: preferredColorScheme as ColorScheme?,
|
||||
cookieBannerHandlingMode:
|
||||
cookieBannerHandlingMode == const $CopyWithPlaceholder()
|
||||
? _value.cookieBannerHandlingMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: cookieBannerHandlingMode as CookieBannerHandlingMode?,
|
||||
cookieBannerHandlingModePrivateBrowsing:
|
||||
cookieBannerHandlingModePrivateBrowsing ==
|
||||
const $CopyWithPlaceholder()
|
||||
? _value.cookieBannerHandlingModePrivateBrowsing
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: cookieBannerHandlingModePrivateBrowsing
|
||||
as CookieBannerHandlingMode?,
|
||||
cookieBannerHandlingGlobalRules:
|
||||
cookieBannerHandlingGlobalRules == const $CopyWithPlaceholder()
|
||||
? _value.cookieBannerHandlingGlobalRules
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: cookieBannerHandlingGlobalRules as bool?,
|
||||
cookieBannerHandlingGlobalRulesSubFrames:
|
||||
cookieBannerHandlingGlobalRulesSubFrames ==
|
||||
const $CopyWithPlaceholder()
|
||||
? _value.cookieBannerHandlingGlobalRulesSubFrames
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: cookieBannerHandlingGlobalRulesSubFrames as bool?,
|
||||
webContentIsolationStrategy:
|
||||
webContentIsolationStrategy == const $CopyWithPlaceholder()
|
||||
? _value.webContentIsolationStrategy
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: webContentIsolationStrategy as WebContentIsolationStrategy?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $EngineSettingsCopyWith on EngineSettings {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfEngineSettings.copyWith(...)` or like so:`instanceOfEngineSettings.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$EngineSettingsCWProxy get copyWith => _$EngineSettingsCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
EngineSettings _$EngineSettingsFromJson(Map<String, dynamic> json) =>
|
||||
EngineSettings.withDefaults(
|
||||
javascriptEnabled: json['javascriptEnabled'] as bool?,
|
||||
trackingProtectionPolicy: $enumDecodeNullable(
|
||||
_$TrackingProtectionPolicyEnumMap, json['trackingProtectionPolicy']),
|
||||
httpsOnlyMode:
|
||||
$enumDecodeNullable(_$HttpsOnlyModeEnumMap, json['httpsOnlyMode']),
|
||||
globalPrivacyControlEnabled: json['globalPrivacyControlEnabled'] as bool?,
|
||||
preferredColorScheme: $enumDecodeNullable(
|
||||
_$ColorSchemeEnumMap, json['preferredColorScheme']),
|
||||
cookieBannerHandlingMode: $enumDecodeNullable(
|
||||
_$CookieBannerHandlingModeEnumMap, json['cookieBannerHandlingMode']),
|
||||
cookieBannerHandlingModePrivateBrowsing: $enumDecodeNullable(
|
||||
_$CookieBannerHandlingModeEnumMap,
|
||||
json['cookieBannerHandlingModePrivateBrowsing']),
|
||||
cookieBannerHandlingGlobalRules:
|
||||
json['cookieBannerHandlingGlobalRules'] as bool?,
|
||||
cookieBannerHandlingGlobalRulesSubFrames:
|
||||
json['cookieBannerHandlingGlobalRulesSubFrames'] as bool?,
|
||||
webContentIsolationStrategy: $enumDecodeNullable(
|
||||
_$WebContentIsolationStrategyEnumMap,
|
||||
json['webContentIsolationStrategy']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$EngineSettingsToJson(EngineSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'javascriptEnabled': instance.javascriptEnabled,
|
||||
'trackingProtectionPolicy':
|
||||
_$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy],
|
||||
'httpsOnlyMode': _$HttpsOnlyModeEnumMap[instance.httpsOnlyMode],
|
||||
'globalPrivacyControlEnabled': instance.globalPrivacyControlEnabled,
|
||||
'preferredColorScheme':
|
||||
_$ColorSchemeEnumMap[instance.preferredColorScheme],
|
||||
'cookieBannerHandlingMode':
|
||||
_$CookieBannerHandlingModeEnumMap[instance.cookieBannerHandlingMode],
|
||||
'cookieBannerHandlingModePrivateBrowsing':
|
||||
_$CookieBannerHandlingModeEnumMap[
|
||||
instance.cookieBannerHandlingModePrivateBrowsing],
|
||||
'cookieBannerHandlingGlobalRules':
|
||||
instance.cookieBannerHandlingGlobalRules,
|
||||
'cookieBannerHandlingGlobalRulesSubFrames':
|
||||
instance.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
'webContentIsolationStrategy': _$WebContentIsolationStrategyEnumMap[
|
||||
instance.webContentIsolationStrategy],
|
||||
};
|
||||
|
||||
const _$TrackingProtectionPolicyEnumMap = {
|
||||
TrackingProtectionPolicy.none: 'none',
|
||||
TrackingProtectionPolicy.recommended: 'recommended',
|
||||
TrackingProtectionPolicy.strict: 'strict',
|
||||
TrackingProtectionPolicy.custom: 'custom',
|
||||
};
|
||||
|
||||
const _$HttpsOnlyModeEnumMap = {
|
||||
HttpsOnlyMode.disabled: 'disabled',
|
||||
HttpsOnlyMode.privateOnly: 'privateOnly',
|
||||
HttpsOnlyMode.enabled: 'enabled',
|
||||
};
|
||||
|
||||
const _$ColorSchemeEnumMap = {
|
||||
ColorScheme.system: 'system',
|
||||
ColorScheme.light: 'light',
|
||||
ColorScheme.dark: 'dark',
|
||||
};
|
||||
|
||||
const _$CookieBannerHandlingModeEnumMap = {
|
||||
CookieBannerHandlingMode.disabled: 'disabled',
|
||||
CookieBannerHandlingMode.rejectAll: 'rejectAll',
|
||||
CookieBannerHandlingMode.rejectOrAcceptAll: 'rejectOrAcceptAll',
|
||||
};
|
||||
|
||||
const _$WebContentIsolationStrategyEnumMap = {
|
||||
WebContentIsolationStrategy.isolateNothing: 'isolateNothing',
|
||||
WebContentIsolationStrategy.isolateEverything: 'isolateEverything',
|
||||
WebContentIsolationStrategy.isolateHighValue: 'isolateHighValue',
|
||||
};
|
||||
@@ -29,6 +29,8 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
||||
final Widget? Function(T item)? itemAvatar;
|
||||
final int? Function(T item)? itemBadgeCount;
|
||||
|
||||
final Widget Function(Widget child, S item)? itemWrap;
|
||||
|
||||
final void Function(T item)? onSelected;
|
||||
final void Function(T item)? onDeleted;
|
||||
|
||||
@@ -37,6 +39,7 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
||||
required this.itemLabel,
|
||||
this.itemAvatar,
|
||||
this.itemBadgeCount,
|
||||
this.itemWrap,
|
||||
required this.availableItems,
|
||||
this.selectedItem,
|
||||
this.maxCount = 25,
|
||||
@@ -76,7 +79,7 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return Padding(
|
||||
final child = Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: _BadgeWrapper(
|
||||
count: itemBadgeCount?.call(item),
|
||||
@@ -101,6 +104,8 @@ class SelectableChips<T extends S, S, K> extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return (itemWrap != null) ? itemWrap!(child, item) : child;
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
+4
-2
@@ -30,9 +30,11 @@ dependencies:
|
||||
flutter_mozilla_components:
|
||||
path: ../packages/flutter_mozilla_components
|
||||
flutter_pdf_text: ^0.8.1
|
||||
flutter_reorderable_grid_view:
|
||||
path: ../../flutter-reorderable-grid-view
|
||||
flutter_secure_storage: ^10.0.0-beta.4
|
||||
flutter_sharing_intent: ^1.1.1
|
||||
go_router: ^14.6.3
|
||||
go_router: ^14.7.1
|
||||
google_fonts: ^6.2.1
|
||||
home_widget: ^0.7.0
|
||||
hooks_riverpod: ^2.6.1
|
||||
@@ -54,7 +56,6 @@ dependencies:
|
||||
path_provider: ^2.1.5
|
||||
pocketbase: ^0.21.0
|
||||
provider: ^6.1.2
|
||||
reorderable_grid: ^1.0.10
|
||||
riverpod: ^2.6.1
|
||||
riverpod_annotation: ^2.6.1
|
||||
rxdart: ^0.28.0
|
||||
@@ -103,6 +104,7 @@ flutter:
|
||||
- assets/icon/
|
||||
- assets/landing/
|
||||
- assets/fonts/
|
||||
- assets/preferences/
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: "launcher_icon"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# melos_managed_dependency_overrides: flutter_mozilla_components,sqlite3_vec,flutter_chat_core
|
||||
dependency_overrides:
|
||||
flutter_chat_core:
|
||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
||||
flutter_mozilla_components:
|
||||
path: ../packages/flutter_mozilla_components
|
||||
sqlite3_vec:
|
||||
path: ../packages/sqlite3_vec
|
||||
flutter_chat_core:
|
||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
||||
|
||||
Reference in New Issue
Block a user