push
This commit is contained in:
@@ -21,6 +21,8 @@ linter:
|
||||
- collection_methods_unrelated_type
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
overridden_fields: ignore
|
||||
plugins:
|
||||
- custom_lint
|
||||
exclude:
|
||||
|
||||
@@ -1,34 +1,4 @@
|
||||
{
|
||||
"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.",
|
||||
@@ -114,6 +84,11 @@
|
||||
"value": true,
|
||||
"title": "Force WebRTC inside the proxy"
|
||||
},
|
||||
"media.peerconnection.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable WebRTC PeerConnection",
|
||||
"description": "Prevent exposing local IP Address"
|
||||
},
|
||||
"media.peerconnection.ice.default_address_only": {
|
||||
"value": true,
|
||||
"title": "Force a single network interface for ICE candidates generation",
|
||||
@@ -121,30 +96,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -202,6 +153,135 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Privacy": {
|
||||
"preferences": {
|
||||
"network.http.referer.XOriginTrimmingPolicy": {
|
||||
"value": 2,
|
||||
"title": "Trim cross-origin referrers",
|
||||
"description": "Trims down referrers to just the scheme, hostname and port"
|
||||
},
|
||||
"network.http.referer.XOriginPolicy": {
|
||||
"value": 2,
|
||||
"title": "Only send cross-origin referer if host matches"
|
||||
},
|
||||
"network.http.referer.disallowCrossSiteRelaxingDefault": {
|
||||
"value": true,
|
||||
"title": "Disallow relaxing referrer policy for cross-site requests"
|
||||
},
|
||||
"network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation": {
|
||||
"value": true,
|
||||
"title": "Disallow relaxing referrer policy for cross-site requests",
|
||||
"description": "On top navigation"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"privacy.query_stripping.enabled": {
|
||||
"value": true,
|
||||
"title": "Strip known tracking query parameters from URLs"
|
||||
},
|
||||
"dom.private-attribution.submission.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable Private Attribution Submission Control",
|
||||
"shouldBeDefault": true
|
||||
},
|
||||
"privacy.trackingprotection.enabled": {
|
||||
"value": true,
|
||||
"title": "Enable Tracking Protection"
|
||||
},
|
||||
"privacy.trackingprotection.socialtracking.enabled": {
|
||||
"value": true,
|
||||
"title": "Enable Social Tracking Protection"
|
||||
},
|
||||
"privacy.trackingprotection.cryptomining.enabled": {
|
||||
"value": true,
|
||||
"title": "Enable Cryptomining Protection"
|
||||
},
|
||||
"privacy.trackingprotection.fingerprinting.enabled": {
|
||||
"value": true,
|
||||
"title": "Enable Fingerprinting Tracking Protection"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Resist Fingerprinting": {
|
||||
"description": "Advanced Fingerprinting protection. This might break websites. For best results make sure to enable \"Privacy\" hardenings.",
|
||||
"preferences": {
|
||||
"privacy.resistFingerprinting": {
|
||||
"value": true,
|
||||
"title": "Resist Fingerprinting"
|
||||
},
|
||||
"privacy.resistFingerprinting.letterboxing": {
|
||||
"value": true,
|
||||
"title": "Enable Letterboxing"
|
||||
},
|
||||
"webgl.disabled": {
|
||||
"value": true,
|
||||
"title": "Disable WebGL (Web Graphics Library)"
|
||||
},
|
||||
"privacy.partition.network_state.ocsp_cache": {
|
||||
"value": true,
|
||||
"title": "Enable OCSP cache"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Attack Surface Reduction": {
|
||||
"preferences": {
|
||||
"pdfjs.enableScripting": {
|
||||
"value": false,
|
||||
"title": "Disable PDFJS scripting"
|
||||
},
|
||||
"mathml.disabled": {
|
||||
"value": true,
|
||||
"title": "Disable MathML (Mathematical Markup Language)"
|
||||
},
|
||||
"gfx.font_rendering.graphite.enabled": {
|
||||
"value": false,
|
||||
"title": "Disable graphite"
|
||||
},
|
||||
"javascript.options.ion": {
|
||||
"value": false,
|
||||
"title": "Disable Ion"
|
||||
},
|
||||
"javascript.options.baselinejit": {
|
||||
"value": false,
|
||||
"title": "Disable baseline JIT"
|
||||
},
|
||||
"javascript.options.jit_trustedprincipals": {
|
||||
"value": false,
|
||||
"title": "Disable JIT trustedprincipals"
|
||||
},
|
||||
"javascript.options.asmjs": {
|
||||
"value": false,
|
||||
"title": "Disable asm.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Miscelaneous": {
|
||||
"preferences": {
|
||||
"permissions.manager.defaultsUrl": {
|
||||
@@ -212,48 +292,13 @@
|
||||
"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": {
|
||||
"layout.css.visited_links_enabled": {
|
||||
"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
|
||||
"title": "Disable coloring of visited links"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,16 @@ import 'package:lensai/features/chat_archive/presentation/screens/list.dart';
|
||||
import 'package:lensai/features/chat_archive/presentation/screens/search.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/screens/browser.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_edit.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/bang_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/general_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/web_engine_hardening.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/web_engine_hardening_group.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/web_engine_settings.dart';
|
||||
import 'package:lensai/features/user/presentation/screens/auth.dart';
|
||||
|
||||
part 'routes.g.dart';
|
||||
@@ -63,6 +68,16 @@ part 'routes.g.dart';
|
||||
TypedGoRoute<ContainerListRoute>(
|
||||
name: 'ContainerListRoute',
|
||||
path: 'containers',
|
||||
routes: [
|
||||
TypedGoRoute<ContainerCreateRoute>(
|
||||
name: 'ContainerCreateRoute',
|
||||
path: 'create',
|
||||
),
|
||||
TypedGoRoute<ContainerEditRoute>(
|
||||
name: 'ContainerEditRoute',
|
||||
path: 'edit',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -179,17 +194,49 @@ class ContainerListRoute extends GoRouteData {
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerEditRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return ContainerEditScreen.edit(
|
||||
initialContainer: state.extra! as ContainerData,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerCreateRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return ContainerEditScreen.create(
|
||||
initialContainer: state.extra! as ContainerData,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@TypedGoRoute<SettingsRoute>(
|
||||
name: 'SettingsRoute',
|
||||
path: '/settings',
|
||||
routes: [
|
||||
TypedGoRoute<BrowserHardeningRoute>(
|
||||
name: 'BrowserHardeningRoute',
|
||||
path: 'hardening',
|
||||
TypedGoRoute<GeneralSettingsRoute>(
|
||||
name: 'GeneralSettingsRoute',
|
||||
path: 'general',
|
||||
),
|
||||
TypedGoRoute<BangSettingsRoute>(
|
||||
name: 'BangSettingsRoute',
|
||||
path: 'bang',
|
||||
),
|
||||
TypedGoRoute<WebEngineSettingsRoute>(
|
||||
name: 'WebEngineSettingsRoute',
|
||||
path: 'web_engine',
|
||||
routes: [
|
||||
TypedGoRoute<BrowserHardeningGroupRoute>(
|
||||
name: 'BrowserHardeningGroupRoute',
|
||||
path: 'group/:group',
|
||||
TypedGoRoute<WebEngineHardeningRoute>(
|
||||
name: 'WebEngineHardeningRoute',
|
||||
path: 'hardening',
|
||||
routes: [
|
||||
TypedGoRoute<WebEngineHardeningGroupRoute>(
|
||||
name: 'WebEngineHardeningGroupRoute',
|
||||
path: 'group/:group',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -202,21 +249,42 @@ class SettingsRoute extends GoRouteData {
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserHardeningRoute extends GoRouteData {
|
||||
class GeneralSettingsRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const BrowserHardeningScreen();
|
||||
return const GeneralSettingsScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserHardeningGroupRoute extends GoRouteData {
|
||||
class BangSettingsRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const BangSettingsScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class WebEngineSettingsRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const WebEngineSettingsScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class WebEngineHardeningRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const WebEngineHardeningScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class WebEngineHardeningGroupRoute extends GoRouteData {
|
||||
final String group;
|
||||
|
||||
const BrowserHardeningGroupRoute({required this.group});
|
||||
const WebEngineHardeningGroupRoute({required this.group});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return BrowserHardeningGroupScreen(groupName: group);
|
||||
return WebEngineHardeningGroupScreen(groupName: group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,18 @@ RouteBase get $browserRoute => GoRouteData.$route(
|
||||
path: 'containers',
|
||||
name: 'ContainerListRoute',
|
||||
factory: $ContainerListRouteExtension._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'create',
|
||||
name: 'ContainerCreateRoute',
|
||||
factory: $ContainerCreateRouteExtension._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'edit',
|
||||
name: 'ContainerEditRoute',
|
||||
factory: $ContainerEditRouteExtension._fromState,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -255,20 +267,73 @@ extension $ContainerListRouteExtension on ContainerListRoute {
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
extension $ContainerCreateRouteExtension on ContainerCreateRoute {
|
||||
static ContainerCreateRoute _fromState(GoRouterState state) =>
|
||||
ContainerCreateRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/containers/create',
|
||||
);
|
||||
|
||||
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 $ContainerEditRouteExtension on ContainerEditRoute {
|
||||
static ContainerEditRoute _fromState(GoRouterState state) =>
|
||||
ContainerEditRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/containers/edit',
|
||||
);
|
||||
|
||||
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 $settingsRoute => GoRouteData.$route(
|
||||
path: '/settings',
|
||||
name: 'SettingsRoute',
|
||||
factory: $SettingsRouteExtension._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'hardening',
|
||||
name: 'BrowserHardeningRoute',
|
||||
factory: $BrowserHardeningRouteExtension._fromState,
|
||||
path: 'general',
|
||||
name: 'GeneralSettingsRoute',
|
||||
factory: $GeneralSettingsRouteExtension._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'bang',
|
||||
name: 'BangSettingsRoute',
|
||||
factory: $BangSettingsRouteExtension._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'web_engine',
|
||||
name: 'WebEngineSettingsRoute',
|
||||
factory: $WebEngineSettingsRouteExtension._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'group/:group',
|
||||
name: 'BrowserHardeningGroupRoute',
|
||||
factory: $BrowserHardeningGroupRouteExtension._fromState,
|
||||
path: 'hardening',
|
||||
name: 'WebEngineHardeningRoute',
|
||||
factory: $WebEngineHardeningRouteExtension._fromState,
|
||||
routes: [
|
||||
GoRouteData.$route(
|
||||
path: 'group/:group',
|
||||
name: 'WebEngineHardeningGroupRoute',
|
||||
factory: $WebEngineHardeningGroupRouteExtension._fromState,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -292,12 +357,12 @@ extension $SettingsRouteExtension on SettingsRoute {
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
extension $BrowserHardeningRouteExtension on BrowserHardeningRoute {
|
||||
static BrowserHardeningRoute _fromState(GoRouterState state) =>
|
||||
BrowserHardeningRoute();
|
||||
extension $GeneralSettingsRouteExtension on GeneralSettingsRoute {
|
||||
static GeneralSettingsRoute _fromState(GoRouterState state) =>
|
||||
GeneralSettingsRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/hardening',
|
||||
'/settings/general',
|
||||
);
|
||||
|
||||
void go(BuildContext context) => context.go(location);
|
||||
@@ -310,14 +375,69 @@ extension $BrowserHardeningRouteExtension on BrowserHardeningRoute {
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
extension $BrowserHardeningGroupRouteExtension on BrowserHardeningGroupRoute {
|
||||
static BrowserHardeningGroupRoute _fromState(GoRouterState state) =>
|
||||
BrowserHardeningGroupRoute(
|
||||
extension $BangSettingsRouteExtension on BangSettingsRoute {
|
||||
static BangSettingsRoute _fromState(GoRouterState state) =>
|
||||
BangSettingsRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/bang',
|
||||
);
|
||||
|
||||
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 $WebEngineSettingsRouteExtension on WebEngineSettingsRoute {
|
||||
static WebEngineSettingsRoute _fromState(GoRouterState state) =>
|
||||
WebEngineSettingsRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/web_engine',
|
||||
);
|
||||
|
||||
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 $WebEngineHardeningRouteExtension on WebEngineHardeningRoute {
|
||||
static WebEngineHardeningRoute _fromState(GoRouterState state) =>
|
||||
WebEngineHardeningRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/web_engine/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 $WebEngineHardeningGroupRouteExtension
|
||||
on WebEngineHardeningGroupRoute {
|
||||
static WebEngineHardeningGroupRoute _fromState(GoRouterState state) =>
|
||||
WebEngineHardeningGroupRoute(
|
||||
group: state.pathParameters['group']!,
|
||||
);
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/settings/hardening/group/${Uri.encodeComponent(group)}',
|
||||
'/settings/web_engine/hardening/group/${Uri.encodeComponent(group)}',
|
||||
);
|
||||
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
class ColorJsonConverter implements JsonConverter<Color, int> {
|
||||
const ColorJsonConverter();
|
||||
|
||||
@override
|
||||
Color fromJson(int from) {
|
||||
return Color(from);
|
||||
}
|
||||
|
||||
@override
|
||||
int toJson(Color value) {
|
||||
return value.value;
|
||||
}
|
||||
}
|
||||
|
||||
class ColorConverter extends TypeConverter<Color, int> {
|
||||
const ColorConverter();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lexo_rank/lexo_rank.dart';
|
||||
import 'package:lexo_rank/lexo_rank/lexo_rank_bucket.dart';
|
||||
import 'package:sqlite3/common.dart';
|
||||
@@ -27,8 +28,8 @@ String _previousRankOrMiddle(List<Object?> args) {
|
||||
}
|
||||
|
||||
String _reorderAfter(List<Object?> args) {
|
||||
final first = (args[0] != null) ? LexoRank.parse(args[0]! as String) : null;
|
||||
final last = (args[1] != null) ? LexoRank.parse(args[1]! as String) : null;
|
||||
final first = args[0].mapNotNull((arg) => LexoRank.parse(arg as String));
|
||||
final last = args[1].mapNotNull((arg) => LexoRank.parse(arg as String));
|
||||
|
||||
if (first == null) {
|
||||
throw Exception('Tab not found');
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:html/parser.dart' as html_parser;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lensai/core/http_error_handler.dart';
|
||||
import 'package:lensai/data/models/web_page_info.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
import 'package:lensai/utils/lru_cache.dart';
|
||||
@@ -267,7 +268,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
url.origin,
|
||||
await BrowserIcon.fromBytes(
|
||||
result.image,
|
||||
dominantColor: (result.color != null) ? Color(result.color!) : null,
|
||||
dominantColor: result.color.mapNotNull((color) => Color(color)),
|
||||
source: result.source,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'generic_website.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$genericWebsiteServiceHash() =>
|
||||
r'8eab8ab1448d4826dc0cda0ff6b47143735ba176';
|
||||
r'27430ba17a04b0fa3cca20abf00f9d1c15fa843a';
|
||||
|
||||
/// See also [GenericWebsiteService].
|
||||
@ProviderFor(GenericWebsiteService)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
extension NullableX<T> on T? {
|
||||
@pragma('vm:prefer-inline')
|
||||
R? mapNotNull<R>(R? Function(T) callback) {
|
||||
// ignore: null_check_on_nullable_type_parameter
|
||||
return (this != null) ? callback(this!) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/bangs/data/database/database.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
@@ -20,7 +21,7 @@ class BangDao extends DatabaseAccessor<BangDatabase> with _$BangDaoMixin {
|
||||
|
||||
SingleSelectable<int> getBangCount({Iterable<BangGroup>? groups}) {
|
||||
return db.bang.count(
|
||||
where: (groups != null) ? (t) => t.group.isInValues(groups) : null,
|
||||
where: groups.mapNotNull((groups) => (t) => t.group.isInValues(groups)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:convert';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:lensai/domain/services/generic_website.dart';
|
||||
import 'package:lensai/features/bangs/data/database/database.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/data/models/search_history_entry.dart';
|
||||
@@ -14,23 +13,27 @@ part 'data.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class BangDataRepository extends _$BangDataRepository {
|
||||
late BangDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(bangDatabaseProvider);
|
||||
}
|
||||
void build() {}
|
||||
|
||||
Stream<BangData?> watchBang(String? trigger) {
|
||||
if (trigger != null) {
|
||||
return _db.bangDao.getBangData(trigger).watchSingleOrNull();
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.getBangData(trigger)
|
||||
.watchSingleOrNull();
|
||||
} else {
|
||||
return Stream.value(null);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<Map<String, List<String>>> watchCategories() {
|
||||
return _db.categoriesJson().watchSingle().map((json) {
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.categoriesJson()
|
||||
.watchSingle()
|
||||
.map((json) {
|
||||
final decoded = jsonDecode(json) as Map<String, dynamic>;
|
||||
return decoded.map(
|
||||
(key, value) => MapEntry(key, (value as List<dynamic>).cast()),
|
||||
@@ -39,7 +42,10 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
}
|
||||
|
||||
Stream<int> watchBangCount(BangGroup group) {
|
||||
return _db.bangDao.getBangCount(groups: [group]).watchSingle();
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.getBangCount(groups: [group]).watchSingle();
|
||||
}
|
||||
|
||||
Stream<List<BangData>> watchBangs({
|
||||
@@ -48,7 +54,9 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
({String category, String? subCategory})? categoryFilter,
|
||||
bool? orderMostFrequentFirst,
|
||||
}) {
|
||||
return _db.bangDao
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.getBangDataList(
|
||||
groups: groups,
|
||||
domain: domain,
|
||||
@@ -60,15 +68,25 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
}
|
||||
|
||||
Stream<List<BangData>> watchFrequentBangs({Iterable<BangGroup>? groups}) {
|
||||
return _db.bangDao.getFrequentBangDataList(groups: groups).watch();
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.getFrequentBangDataList(groups: groups)
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<List<SearchHistoryEntry>> watchSearchHistory({required int limit}) {
|
||||
return _db.searchHistoryEntries(limit: limit).watch();
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.searchHistoryEntries(limit: limit)
|
||||
.watch();
|
||||
}
|
||||
|
||||
Future<void> increaseFrequency(String trigger) {
|
||||
return _db.bangDao.increaseBangFrequency(trigger);
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.increaseBangFrequency(trigger);
|
||||
}
|
||||
|
||||
Future<void> addSearchEntry(
|
||||
@@ -76,17 +94,21 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
String searchQuery, {
|
||||
required int maxEntryCount,
|
||||
}) {
|
||||
final db = ref.read(bangDatabaseProvider);
|
||||
//Pack in a transaction to bundle rebuilds of watch() queries
|
||||
return _db.transaction(
|
||||
return db.transaction(
|
||||
() async {
|
||||
await _db.bangDao.addSearchEntry(trigger, searchQuery);
|
||||
await _db.evictHistoryEntries(limit: maxEntryCount);
|
||||
await db.bangDao.addSearchEntry(trigger, searchQuery);
|
||||
await db.evictHistoryEntries(limit: maxEntryCount);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeSearchEntry(String searchQuery) {
|
||||
return _db.bangDao.removeSearchEntry(searchQuery);
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.removeSearchEntry(searchQuery);
|
||||
}
|
||||
|
||||
Future<Result<BangData>> ensureIconAvailable(BangData bang) async {
|
||||
@@ -110,10 +132,13 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
}
|
||||
|
||||
Future<int> resetFrequencies() {
|
||||
return _db.bangFrequency.deleteAll();
|
||||
return ref.read(bangDatabaseProvider).bangFrequency.deleteAll();
|
||||
}
|
||||
|
||||
Future<int> resetFrequency(String trigger) {
|
||||
return _db.bangFrequency.deleteWhere((t) => t.trigger.equals(trigger));
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangFrequency
|
||||
.deleteWhere((t) => t.trigger.equals(trigger));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'data.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangDataRepositoryHash() =>
|
||||
r'b51786552a812ddd54d1418450fe03ff89351e28';
|
||||
r'2c009519cf02df4d3cbb6768dbe55e9eb696e126';
|
||||
|
||||
/// See also [BangDataRepository].
|
||||
@ProviderFor(BangDataRepository)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/features/bangs/data/database/database.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/data/providers.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
@@ -11,17 +10,20 @@ part 'search.g.dart';
|
||||
@Riverpod()
|
||||
class BangSearch extends _$BangSearch {
|
||||
late StreamController<List<BangData>> _streamController;
|
||||
late BangDatabase _db;
|
||||
|
||||
Future<void> search(String input) async {
|
||||
if (input.isNotEmpty) {
|
||||
await _db.bangDao.queryBangs(input).get().then(_streamController.add);
|
||||
await ref
|
||||
.read(bangDatabaseProvider)
|
||||
.bangDao
|
||||
.queryBangs(input)
|
||||
.get()
|
||||
.then(_streamController.add);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<BangData>> build() {
|
||||
_db = ref.watch(bangDatabaseProvider);
|
||||
_streamController = StreamController();
|
||||
|
||||
ref.onDispose(() async {
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'search.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangSearchHash() => r'abd71f10288ac1f559e66fc11035f9f2b027c353';
|
||||
String _$bangSearchHash() => r'632ddd09a1198394884cb5c27a2bde7850c0515b';
|
||||
|
||||
/// See also [BangSearch].
|
||||
@ProviderFor(BangSearch)
|
||||
|
||||
@@ -11,10 +11,6 @@ part 'sync.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class BangSyncRepository extends _$BangSyncRepository {
|
||||
late BangDatabase _db;
|
||||
|
||||
BangSyncRepository();
|
||||
|
||||
static Future<Result<void>> _fetchAndSync({
|
||||
required BangSourceService sourceService,
|
||||
required BangDatabase db,
|
||||
@@ -51,22 +47,22 @@ class BangSyncRepository extends _$BangSyncRepository {
|
||||
) async {
|
||||
try {
|
||||
return Result.success(
|
||||
await _db.computeWithDatabase(
|
||||
connect: BangDatabase.new,
|
||||
computation: (db) async {
|
||||
final ref = ProviderContainer();
|
||||
final result = await _fetchAndSync(
|
||||
sourceService: ref.read(bangSourceServiceProvider.notifier),
|
||||
db: db,
|
||||
url: Uri.parse(group.url),
|
||||
group: group,
|
||||
syncInterval: syncInterval,
|
||||
);
|
||||
await ref.read(bangDatabaseProvider).computeWithDatabase(
|
||||
connect: BangDatabase.new,
|
||||
computation: (db) async {
|
||||
final ref = ProviderContainer();
|
||||
final result = await _fetchAndSync(
|
||||
sourceService: ref.read(bangSourceServiceProvider.notifier),
|
||||
db: db,
|
||||
url: Uri.parse(group.url),
|
||||
group: group,
|
||||
syncInterval: syncInterval,
|
||||
);
|
||||
|
||||
//Throw if necessary
|
||||
return result.value;
|
||||
},
|
||||
),
|
||||
//Throw if necessary
|
||||
return result.value;
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return Result.failure(
|
||||
@@ -80,7 +76,11 @@ class BangSyncRepository extends _$BangSyncRepository {
|
||||
}
|
||||
|
||||
Stream<DateTime?> watchLastSyncOfGroup(BangGroup group) {
|
||||
return _db.syncDao.lastSyncOfGroup(group).watchSingleOrNull();
|
||||
return ref
|
||||
.read(bangDatabaseProvider)
|
||||
.syncDao
|
||||
.lastSyncOfGroup(group)
|
||||
.watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<Map<BangGroup, Result<void>>> syncBangGroups({
|
||||
@@ -100,7 +100,5 @@ class BangSyncRepository extends _$BangSyncRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(bangDatabaseProvider);
|
||||
}
|
||||
void build() {}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'sync.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangSyncRepositoryHash() =>
|
||||
r'86933b05d01629c67ed0443df91e991c40136423';
|
||||
r'a46813f953e550585f432b11c364d87791819c34';
|
||||
|
||||
/// See also [BangSyncRepository].
|
||||
@ProviderFor(BangSyncRepository)
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.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/extensions/nullable.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
@@ -21,9 +22,8 @@ class BangListScreen extends HookConsumerWidget {
|
||||
final bangsAsync = ref.watch(
|
||||
bangDataListProvider(
|
||||
filter: (
|
||||
categoryFilter: (category != null)
|
||||
? (category: category!, subCategory: subCategory)
|
||||
: null,
|
||||
categoryFilter: category.mapNotNull(
|
||||
(category) => (category: category, subCategory: subCategory)),
|
||||
domain: null,
|
||||
groups: null,
|
||||
orderMostFrequentFirst: null,
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
@@ -20,9 +20,7 @@ class BangSearchScreen extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final resultsAsync = ref.watch(bangSearchProvider);
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
|
||||
|
||||
final textEditingController =
|
||||
useTextEditingController(text: initialSearchText);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/database.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -8,16 +7,15 @@ part 'chat_metadata.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class ChatMetadataRepository extends _$ChatMetadataRepository {
|
||||
late ChatDatabase _db;
|
||||
|
||||
Future<Result<void>> updateMetadata(ChatMetadata metadata) {
|
||||
return Result.fromAsync(() {
|
||||
return _db.metadataDao.updateChatMetadata(chatId, metadata);
|
||||
return ref
|
||||
.read(chatDatabaseProvider)
|
||||
.metadataDao
|
||||
.updateChatMetadata(chatId, metadata);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void build(String chatId) {
|
||||
_db = ref.watch(chatDatabaseProvider);
|
||||
}
|
||||
void build(String chatId) {}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'chat_metadata.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatMetadataRepositoryHash() =>
|
||||
r'72a0226ed7e87f452f4dfe44594bb30663c2db45';
|
||||
r'bd6b28b9d034ee82f95e974e4bf8f553546e835a';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
|
||||
class ChatEntity {
|
||||
static final _namePattern = RegExp(r"^(.*?) - (.*?)\.md$");
|
||||
|
||||
@@ -14,7 +16,7 @@ class ChatEntity {
|
||||
return ChatEntity._(
|
||||
fileName,
|
||||
name: match?.group(1),
|
||||
dateTime: (match != null) ? DateTime.tryParse(match.group(2)!) : null,
|
||||
dateTime: match.mapNotNull((match) => DateTime.tryParse(match.group(2)!)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ part 'search.g.dart';
|
||||
class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository {
|
||||
late Completer<void> _populatedCompleter;
|
||||
late StreamController<List<ChatQueryResult>> _streamController;
|
||||
late ChatSearchDatabase _searchDatabase;
|
||||
|
||||
Future<ChatCompanion?> _readChat(ChatEntity chat) async {
|
||||
final contentResult = await ref
|
||||
@@ -64,7 +63,9 @@ class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository {
|
||||
}) async {
|
||||
if (input.isNotEmpty) {
|
||||
await _populatedCompleter.future;
|
||||
await _searchDatabase.searchDao
|
||||
await ref
|
||||
.read(chatSearchDatabaseProvider)
|
||||
.searchDao
|
||||
.queryChats(
|
||||
searchString: input,
|
||||
snippetLength: snippetLength,
|
||||
@@ -81,11 +82,12 @@ class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository {
|
||||
Stream<List<ChatQueryResult>> build() async* {
|
||||
_populatedCompleter = Completer();
|
||||
_streamController = StreamController();
|
||||
_searchDatabase = ref.watch(chatSearchDatabaseProvider);
|
||||
|
||||
final searchDatabase = ref.watch(chatSearchDatabaseProvider);
|
||||
|
||||
// populate with initial chats
|
||||
await _searchDatabase.searchDao.deleteAllChats();
|
||||
await _searchDatabase.searchDao.indexChats(await _availableChats());
|
||||
await searchDatabase.searchDao.deleteAllChats();
|
||||
await searchDatabase.searchDao.indexChats(await _availableChats());
|
||||
_populatedCompleter.complete();
|
||||
|
||||
final changeStreamSubscription =
|
||||
@@ -98,11 +100,11 @@ class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository {
|
||||
if (chat.name != null) {
|
||||
final companion = await _readChat(chat);
|
||||
if (companion != null) {
|
||||
await _searchDatabase.searchDao.upsertChat(companion);
|
||||
await searchDatabase.searchDao.upsertChat(companion);
|
||||
}
|
||||
}
|
||||
case ChangeType.REMOVE:
|
||||
await _searchDatabase.searchDao.deleteChat(chat.fileName);
|
||||
await searchDatabase.searchDao.deleteChat(chat.fileName);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'search.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatArchiveSearchRepositoryHash() =>
|
||||
r'd7174cfb877c61ee4e9580f44d7ffd11bf3b4b60';
|
||||
r'6762958d43d2efc95f5d9848bfcae41436564f2b';
|
||||
|
||||
/// See also [ChatArchiveSearchRepository].
|
||||
@ProviderFor(ChatArchiveSearchRepository)
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/extensions/date_time.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/repositories/archive.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
@@ -41,9 +42,10 @@ class ChatArchiveListScreen extends HookConsumerWidget {
|
||||
|
||||
return ListTile(
|
||||
title: Text(chat.toString()),
|
||||
subtitle: (chat.dateTime != null)
|
||||
? Text(chat.dateTime!.formatWithMinutePrecision())
|
||||
: null,
|
||||
subtitle: chat.dateTime.mapNotNull(
|
||||
(chatTime) =>
|
||||
Text(chatTime.formatWithMinutePrecision()),
|
||||
),
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
ChatArchiveDetailRoute(fileName: chat.fileName)
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/extensions/date_time.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/repositories/search.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
@@ -20,9 +20,7 @@ class ChatArchiveSearchScreen extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final resultsAsync = ref.watch(chatArchiveSearchRepositoryProvider);
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
|
||||
|
||||
final textEditingController = useTextEditingController();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/data/models/web_page_info.dart';
|
||||
import 'package:lensai/domain/entities/equatable_image.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
|
||||
@@ -24,13 +25,13 @@ class TabState extends WebPageInfo with FastEquatable {
|
||||
final EquatableImage? icon;
|
||||
|
||||
@override
|
||||
BrowserIcon? get favicon => (icon != null)
|
||||
? BrowserIcon(
|
||||
image: icon!,
|
||||
BrowserIcon? get favicon => icon.mapNotNull(
|
||||
(icon) => BrowserIcon(
|
||||
image: icon,
|
||||
dominantColor: null,
|
||||
source: IconSource.memory,
|
||||
)
|
||||
: null;
|
||||
),
|
||||
);
|
||||
|
||||
final EquatableImage? thumbnail;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/extensions/image.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
|
||||
@@ -39,10 +40,10 @@ class TabStates extends _$TabStates {
|
||||
Future<void> _onIconChange(IconChangeEvent event) async {
|
||||
final IconChangeEvent(:tabId, :bytes) = event;
|
||||
|
||||
final image = (bytes != null)
|
||||
? (await tryDecodeImage(bytes)
|
||||
.then((image) async => image?.toEquatable()))
|
||||
: null;
|
||||
final image = await bytes.mapNotNull(
|
||||
(bytes) =>
|
||||
tryDecodeImage(bytes).then((image) async => image?.toEquatable()),
|
||||
);
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
state = {...state}..[tabId] = current.copyWith.icon(image);
|
||||
@@ -51,10 +52,10 @@ class TabStates extends _$TabStates {
|
||||
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
|
||||
final ThumbnailEvent(:tabId, :bytes) = event;
|
||||
|
||||
final image = (bytes != null)
|
||||
? (await tryDecodeImage(bytes)
|
||||
.then((image) async => image?.toEquatable()))
|
||||
: null;
|
||||
final image = await bytes.mapNotNull(
|
||||
(bytes) =>
|
||||
tryDecodeImage(bytes).then((image) async => image?.toEquatable()),
|
||||
);
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
|
||||
|
||||
@@ -173,7 +173,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
|
||||
String _$tabStatesHash() => r'77f07f330dfac8dc8d28d18737203c7c122f3b74';
|
||||
String _$tabStatesHash() => r'296d85ec016b22236b5830fce6b01cda81fbc02d';
|
||||
|
||||
/// See also [TabStates].
|
||||
@ProviderFor(TabStates)
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:ui';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/domain/entities/equatable_image.dart';
|
||||
import 'package:lensai/extensions/image.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/web_extension_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/utils/image_helper.dart';
|
||||
@@ -33,12 +34,10 @@ class WebExtensionsState extends _$WebExtensionsState {
|
||||
title: data.title,
|
||||
enabled: data.enabled ?? current.enabled,
|
||||
badgeText: data.badgeText,
|
||||
badgeTextColor: (data.badgeTextColor != null)
|
||||
? Color(data.badgeTextColor!)
|
||||
: null,
|
||||
badgeBackgroundColor: (data.badgeBackgroundColor != null)
|
||||
? Color(data.badgeBackgroundColor!)
|
||||
: null,
|
||||
badgeTextColor:
|
||||
data.badgeTextColor.mapNotNull((color) => Color(color)),
|
||||
badgeBackgroundColor:
|
||||
data.badgeBackgroundColor.mapNotNull((color) => Color(color)),
|
||||
);
|
||||
} else {
|
||||
if (state.containsKey(extensionId)) {
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'web_extensions_state.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$webExtensionsStateHash() =>
|
||||
r'bbaba8d27980046275858d2707bc66c6e349a537';
|
||||
r'4e6dc46e49ff353e1c3cc4616008685c0e21501d';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -2,14 +2,15 @@ import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.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/utils/debouncer.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -19,31 +20,59 @@ part 'tab.g.dart';
|
||||
class TabRepository extends _$TabRepository {
|
||||
final _tabsService = GeckoTabService();
|
||||
|
||||
late TabDatabase _db;
|
||||
|
||||
Future<String> addTab({
|
||||
Uri? url,
|
||||
bool selectTab = true,
|
||||
bool startLoading = true,
|
||||
String? parentId,
|
||||
LoadUrlFlags flags = LoadUrlFlags.NONE,
|
||||
String? contextId,
|
||||
Source source = Internal.newTab,
|
||||
bool private = false,
|
||||
HistoryMetadataKey? historyMetadata,
|
||||
Map<String, String>? additionalHeaders,
|
||||
}) {
|
||||
return _tabsService.addTab(
|
||||
url: url,
|
||||
selectTab: selectTab,
|
||||
startLoading: startLoading,
|
||||
parentId: parentId,
|
||||
flags: flags,
|
||||
contextId: contextId,
|
||||
source: source,
|
||||
private: private,
|
||||
historyMetadata: historyMetadata,
|
||||
additionalHeaders: additionalHeaders,
|
||||
}) async {
|
||||
final selectedContainer =
|
||||
await ref.read(selectedContainerProvider.notifier).fetchData();
|
||||
|
||||
return ref.read(tabDatabaseProvider).tabDao.upsertContainerTabTransactional(
|
||||
() {
|
||||
return _tabsService.addTab(
|
||||
url: url,
|
||||
selectTab: selectTab,
|
||||
startLoading: startLoading,
|
||||
parentId: parentId,
|
||||
flags: flags,
|
||||
contextId: selectedContainer?.metadata.contextualIdentity,
|
||||
source: source,
|
||||
private: private,
|
||||
historyMetadata: historyMetadata,
|
||||
additionalHeaders: additionalHeaders,
|
||||
);
|
||||
},
|
||||
containerId: Value(selectedContainer?.id),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> duplicateTab({
|
||||
required String? selectTabId,
|
||||
String? containerId,
|
||||
bool selectTab = true,
|
||||
}) async {
|
||||
final containerData = await containerId.mapNotNull(
|
||||
(containerId) => ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.getContainerData(containerId),
|
||||
);
|
||||
|
||||
return ref.read(tabDatabaseProvider).tabDao.upsertContainerTabTransactional(
|
||||
() {
|
||||
return _tabsService.duplicateTab(
|
||||
selectTabId: selectTabId,
|
||||
newContextId: containerData?.metadata.contextualIdentity,
|
||||
selectNewTab: selectTab,
|
||||
);
|
||||
},
|
||||
containerId: Value(containerData?.id),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,18 +96,19 @@ class TabRepository extends _$TabRepository {
|
||||
final tabStateDebouncer = Debouncer(const Duration(seconds: 3));
|
||||
Map<String, TabState>? debounceStartValue;
|
||||
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
final tabAddedSub = eventSerivce.tabAddedStream.listen(
|
||||
(tabId) async {
|
||||
final containerId = ref.read(selectedContainerProvider);
|
||||
await _db.tabDao.upsertTab(tabId, containerId: Value(containerId));
|
||||
await db.tabDao
|
||||
.upsertUnassignedTab(tabId, containerId: Value(containerId));
|
||||
},
|
||||
);
|
||||
|
||||
final tabContentSub =
|
||||
tabContentService.tabContentStream.listen((content) async {
|
||||
await _db.tabDao.updateTabContent(
|
||||
await db.tabDao.updateTabContent(
|
||||
content.tabId,
|
||||
isProbablyReaderable: content.isProbablyReaderable,
|
||||
extractedContentMarkdown: content.extractedContentMarkdown,
|
||||
@@ -92,7 +122,7 @@ class TabRepository extends _$TabRepository {
|
||||
selectedTabProvider,
|
||||
(previous, tabId) async {
|
||||
if (tabId != null) {
|
||||
await _db.tabDao.touchTab(tabId, timestamp: DateTime.now());
|
||||
await db.tabDao.touchTab(tabId, timestamp: DateTime.now());
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -104,7 +134,7 @@ class TabRepository extends _$TabRepository {
|
||||
final syncTabs = next.isNotEmpty || (previous?.isNotEmpty ?? false);
|
||||
|
||||
if (syncTabs) {
|
||||
await _db.tabDao.syncTabs(retainTabIds: next);
|
||||
await db.tabDao.syncTabs(retainTabIds: next);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -120,7 +150,7 @@ class TabRepository extends _$TabRepository {
|
||||
}
|
||||
|
||||
tabStateDebouncer.eventOccured(() async {
|
||||
await _db.tabDao.updateTabs(debounceStartValue, next);
|
||||
await db.tabDao.updateTabs(debounceStartValue, next);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabRepositoryHash() => r'b9caf89fe372b79f37c4a3ebff800ccc2d9289d2';
|
||||
String _$tabRepositoryHash() => r'85af29fa18ea5a4a67fbffbb9478c801dafddac2';
|
||||
|
||||
/// See also [TabRepository].
|
||||
@ProviderFor(TabRepository)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/user/data/models/general_settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'delete_browser_data.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class DeleteBrowserDataService extends _$DeleteBrowserDataService {
|
||||
final _service = GeckoDeleteBrowserDataService();
|
||||
|
||||
Future<void> deleteData(Set<DeleteBrowsingDataType>? types) async {
|
||||
if (types != null) {
|
||||
for (final type in types) {
|
||||
switch (type) {
|
||||
case DeleteBrowsingDataType.tabs:
|
||||
await _service.deleteTabs();
|
||||
case DeleteBrowsingDataType.history:
|
||||
await _service.deleteBrowsingHistory();
|
||||
case DeleteBrowsingDataType.cookies:
|
||||
await _service.deleteCookiesAndSiteData();
|
||||
case DeleteBrowsingDataType.cache:
|
||||
await _service.deleteCachedFiles();
|
||||
case DeleteBrowsingDataType.permissions:
|
||||
await _service.deleteSitePermissions();
|
||||
case DeleteBrowsingDataType.downloads:
|
||||
await _service.deleteDownloads();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'delete_browser_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$deleteBrowserDataServiceHash() =>
|
||||
r'f5fdbac17c14693fceac913591750ca9285b496c';
|
||||
|
||||
/// See also [DeleteBrowserDataService].
|
||||
@ProviderFor(DeleteBrowserDataService)
|
||||
final deleteBrowserDataServiceProvider =
|
||||
AutoDisposeNotifierProvider<DeleteBrowserDataService, void>.internal(
|
||||
DeleteBrowserDataService.new,
|
||||
name: r'deleteBrowserDataServiceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$deleteBrowserDataServiceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$DeleteBrowserDataService = AutoDisposeNotifier<void>;
|
||||
// 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
|
||||
@@ -1,10 +1,90 @@
|
||||
import 'package:flutter/material.dart' show ThemeMode;
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'engine_settings.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
GeckoEngineSettingsService engineSettingsService(Ref ref) {
|
||||
return GeckoEngineSettingsService();
|
||||
class EngineSettingsReplicationService
|
||||
extends _$EngineSettingsReplicationService {
|
||||
final _service = GeckoEngineSettingsService();
|
||||
|
||||
@override
|
||||
void build() {
|
||||
var initialSettingsSent = false;
|
||||
|
||||
ref.listen(
|
||||
generalSettingsRepositoryProvider
|
||||
.select((settings) => settings.themeMode),
|
||||
(previous, next) async {
|
||||
final theme = switch (next) {
|
||||
ThemeMode.system => ColorScheme.system,
|
||||
ThemeMode.light => ColorScheme.light,
|
||||
ThemeMode.dark => ColorScheme.dark,
|
||||
};
|
||||
|
||||
await _service.preferredColorScheme(theme);
|
||||
},
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
engineSettingsRepositoryProvider,
|
||||
(previous, next) async {
|
||||
if (initialSettingsSent && previous != null) {
|
||||
if (previous.javascriptEnabled != next.javascriptEnabled) {
|
||||
await _service.javascriptEnabled(next.javascriptEnabled);
|
||||
}
|
||||
if (previous.trackingProtectionPolicy !=
|
||||
next.trackingProtectionPolicy) {
|
||||
await _service
|
||||
.trackingProtectionPolicy(next.trackingProtectionPolicy);
|
||||
}
|
||||
if (previous.httpsOnlyMode != next.httpsOnlyMode) {
|
||||
await _service.httpsOnlyMode(next.httpsOnlyMode);
|
||||
}
|
||||
if (previous.globalPrivacyControlEnabled !=
|
||||
next.globalPrivacyControlEnabled) {
|
||||
await _service
|
||||
.globalPrivacyControlEnabled(next.globalPrivacyControlEnabled);
|
||||
}
|
||||
if (previous.preferredColorScheme != next.preferredColorScheme) {
|
||||
await _service.preferredColorScheme(next.preferredColorScheme);
|
||||
}
|
||||
if (previous.cookieBannerHandlingMode !=
|
||||
next.cookieBannerHandlingMode) {
|
||||
await _service
|
||||
.cookieBannerHandlingMode(next.cookieBannerHandlingMode);
|
||||
}
|
||||
if (previous.cookieBannerHandlingModePrivateBrowsing !=
|
||||
next.cookieBannerHandlingModePrivateBrowsing) {
|
||||
await _service.cookieBannerHandlingModePrivateBrowsing(
|
||||
next.cookieBannerHandlingModePrivateBrowsing,
|
||||
);
|
||||
}
|
||||
if (previous.cookieBannerHandlingGlobalRules !=
|
||||
next.cookieBannerHandlingGlobalRules) {
|
||||
await _service.cookieBannerHandlingGlobalRules(
|
||||
next.cookieBannerHandlingGlobalRules,
|
||||
);
|
||||
}
|
||||
if (previous.cookieBannerHandlingGlobalRulesSubFrames !=
|
||||
next.cookieBannerHandlingGlobalRulesSubFrames) {
|
||||
await _service.cookieBannerHandlingGlobalRulesSubFrames(
|
||||
next.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
);
|
||||
}
|
||||
if (previous.webContentIsolationStrategy !=
|
||||
next.webContentIsolationStrategy) {
|
||||
await _service
|
||||
.webContentIsolationStrategy(next.webContentIsolationStrategy);
|
||||
}
|
||||
} else {
|
||||
await _service.setDefaultSettings(next);
|
||||
initialSettingsSent = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -6,24 +6,22 @@ part of 'engine_settings.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$engineSettingsServiceHash() =>
|
||||
r'051d7b6f65eb5dcb07405b4af1bdadd8dd74fdc0';
|
||||
String _$engineSettingsReplicationServiceHash() =>
|
||||
r'0729260eeb34b58470622ac9a3a6abb257730a61';
|
||||
|
||||
/// See also [engineSettingsService].
|
||||
@ProviderFor(engineSettingsService)
|
||||
final engineSettingsServiceProvider =
|
||||
Provider<GeckoEngineSettingsService>.internal(
|
||||
engineSettingsService,
|
||||
name: r'engineSettingsServiceProvider',
|
||||
/// See also [EngineSettingsReplicationService].
|
||||
@ProviderFor(EngineSettingsReplicationService)
|
||||
final engineSettingsReplicationServiceProvider =
|
||||
NotifierProvider<EngineSettingsReplicationService, void>.internal(
|
||||
EngineSettingsReplicationService.new,
|
||||
name: r'engineSettingsReplicationServiceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$engineSettingsServiceHash,
|
||||
: _$engineSettingsReplicationServiceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef EngineSettingsServiceRef = ProviderRef<GeckoEngineSettingsService>;
|
||||
typedef _$EngineSettingsReplicationService = Notifier<void>;
|
||||
// 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
|
||||
|
||||
@@ -1,103 +1,104 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
// import 'package:collection/collection.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
// import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
// import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
// import 'package:lensai/extensions/nullable.dart';
|
||||
// import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
// import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
// import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
// import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
|
||||
class TabActionDialog extends HookConsumerWidget {
|
||||
final TabState initialTab;
|
||||
// class TabActionDialog extends HookConsumerWidget {
|
||||
// final TabState initialTab;
|
||||
|
||||
final void Function()? onDismiss;
|
||||
// final void Function()? onDismiss;
|
||||
|
||||
const TabActionDialog({
|
||||
required this.initialTab,
|
||||
this.onDismiss,
|
||||
super.key,
|
||||
});
|
||||
// const TabActionDialog({
|
||||
// required this.initialTab,
|
||||
// this.onDismiss,
|
||||
// super.key,
|
||||
// });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
|
||||
final tabContainerId = ref.watch(
|
||||
tabContainerIdProvider(initialTab.id)
|
||||
.select((value) => value.valueOrNull),
|
||||
);
|
||||
// @override
|
||||
// Widget build(BuildContext context, WidgetRef ref) {
|
||||
// final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
|
||||
// final tabContainerId = ref.watch(
|
||||
// tabContainerIdProvider(initialTab.id)
|
||||
// .select((value) => value.valueOrNull),
|
||||
// );
|
||||
|
||||
final containers = ref
|
||||
.watch(
|
||||
containersWithCountProvider.select(
|
||||
(value) => EquatableCollection(value.valueOrNull, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
// final containers = ref
|
||||
// .watch(
|
||||
// containersWithCountProvider.select(
|
||||
// (value) => EquatableCollection(value.valueOrNull, immutable: true),
|
||||
// ),
|
||||
// )
|
||||
// .collection;
|
||||
|
||||
final selectedContainer = containers
|
||||
?.firstWhereOrNull((container) => container.id == tabContainerId);
|
||||
// final selectedContainer = containers
|
||||
// ?.firstWhereOrNull((container) => container.id == tabContainerId);
|
||||
|
||||
final expansionController = useExpansionTileController();
|
||||
// final expansionController = useExpansionTileController();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
ModalBarrier(
|
||||
color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
|
||||
onDismiss: onDismiss,
|
||||
),
|
||||
SimpleDialog(
|
||||
titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
insetPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
title: ListTile(
|
||||
leading: RawImage(
|
||||
image: tabState.icon?.value,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(tabState.title),
|
||||
subtitle: Text(tabState.url.authority),
|
||||
),
|
||||
children: [
|
||||
SizedBox(
|
||||
//We need this to stretch the dialog, then padding from dialog is applied
|
||||
width: double.maxFinite,
|
||||
child: ExpansionTile(
|
||||
controller: expansionController,
|
||||
leading: (selectedContainer != null)
|
||||
? CircleAvatar(backgroundColor: selectedContainer.color)
|
||||
: null,
|
||||
title: (selectedContainer != null)
|
||||
? Text(selectedContainer.name ?? 'New Container')
|
||||
: const Text('Assign a Container'),
|
||||
children: containers
|
||||
?.where((container) => container.id != tabContainerId)
|
||||
.map(
|
||||
(container) => ListTile(
|
||||
leading:
|
||||
CircleAvatar(backgroundColor: container.color),
|
||||
title: Text(container.name ?? 'New Container'),
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(initialTab.id, container.id);
|
||||
// return Stack(
|
||||
// children: [
|
||||
// ModalBarrier(
|
||||
// color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
|
||||
// onDismiss: onDismiss,
|
||||
// ),
|
||||
// SimpleDialog(
|
||||
// titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
|
||||
// contentPadding: EdgeInsets.zero,
|
||||
// insetPadding: const EdgeInsets.symmetric(
|
||||
// horizontal: 20.0,
|
||||
// vertical: 24.0,
|
||||
// ),
|
||||
// title: ListTile(
|
||||
// leading: RawImage(
|
||||
// image: tabState.icon?.value,
|
||||
// width: 24,
|
||||
// height: 24,
|
||||
// ),
|
||||
// contentPadding: EdgeInsets.zero,
|
||||
// title: Text(tabState.title),
|
||||
// subtitle: Text(tabState.url.authority),
|
||||
// ),
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// //We need this to stretch the dialog, then padding from dialog is applied
|
||||
// width: double.maxFinite,
|
||||
// child: ExpansionTile(
|
||||
// controller: expansionController,
|
||||
// leading: selectedContainer?.color.mapNotNull(
|
||||
// (color) => CircleAvatar(backgroundColor: color),
|
||||
// ),
|
||||
// title: (selectedContainer != null)
|
||||
// ? Text(selectedContainer.name ?? 'New Container')
|
||||
// : const Text('Assign a Container'),
|
||||
// children: containers
|
||||
// ?.where((container) => container.id != tabContainerId)
|
||||
// .map(
|
||||
// (container) => ListTile(
|
||||
// leading:
|
||||
// CircleAvatar(backgroundColor: container.color),
|
||||
// title: Text(container.name ?? 'New Container'),
|
||||
// onTap: () async {
|
||||
// await ref
|
||||
// .read(tabDataRepositoryProvider.notifier)
|
||||
// .assignContainer(initialTab.id, container.id);
|
||||
|
||||
expansionController.collapse();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
// expansionController.collapse();
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
// .toList() ??
|
||||
// [],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
+2
-4
@@ -22,7 +22,7 @@ import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
@@ -40,9 +40,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
|
||||
|
||||
final availableBangsAsync = ref.watch(
|
||||
bangDataListProvider(
|
||||
|
||||
@@ -17,7 +17,6 @@ import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.
|
||||
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/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';
|
||||
@@ -31,7 +30,6 @@ import 'package:lensai/features/geckoview/features/readerview/presentation/widge
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart';
|
||||
import 'package:lensai/presentation/hooks/menu_controller.dart';
|
||||
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
||||
@@ -77,15 +75,8 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
},
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider.select((value) => value.enableJavascript),
|
||||
(previous, next) async {
|
||||
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
|
||||
},
|
||||
);
|
||||
|
||||
// ref.listen(
|
||||
// settingsRepositoryProvider
|
||||
// generalSettingsRepositoryProvider
|
||||
// .select((value) => value.valueOrNull?.kagiSession),
|
||||
// (previous, next) async {
|
||||
// if (next != null && next.isNotEmpty) {
|
||||
@@ -598,7 +589,7 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await context
|
||||
.push(BrowserHardeningRoute().location);
|
||||
.push(WebEngineHardeningRoute().location);
|
||||
},
|
||||
leadingIcon: const Icon(Icons.info),
|
||||
child: const Text('Pref'),
|
||||
@@ -648,7 +639,8 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
: null;
|
||||
|
||||
final tabCount = ref.read(
|
||||
tabListProvider.select((tabs) => tabs.length));
|
||||
tabListProvider.select((tabs) => tabs.length),
|
||||
);
|
||||
|
||||
//Don't do anything if a child route is active
|
||||
if (GoRouterState.of(context).topRoute?.path !=
|
||||
|
||||
@@ -8,13 +8,21 @@ import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/delete_browser_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:lensai/features/user/domain/services/local_authentication.dart';
|
||||
|
||||
class BrowserView extends StatefulHookConsumerWidget {
|
||||
final Duration screenshotPeriod;
|
||||
final FutureOr<void> Function()? postInitializationStep;
|
||||
|
||||
const BrowserView({this.screenshotPeriod = const Duration(seconds: 10)});
|
||||
const BrowserView({
|
||||
this.screenshotPeriod = const Duration(seconds: 10),
|
||||
this.postInitializationStep,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _BrowserViewState();
|
||||
@@ -63,6 +71,15 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
|
||||
return GeckoView(
|
||||
preInitializationStep: () async {
|
||||
await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetch()
|
||||
.then((settings) {
|
||||
ref
|
||||
.read(deleteBrowserDataServiceProvider.notifier)
|
||||
.deleteData(settings.deleteBrowsingDataOnQuit);
|
||||
});
|
||||
|
||||
await ref
|
||||
.read(eventServiceProvider)
|
||||
.viewReadyStateEvents
|
||||
@@ -77,6 +94,9 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
},
|
||||
);
|
||||
},
|
||||
postInitializationStep: () async {
|
||||
await widget.postInitializationStep?.call();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,6 +129,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
cacheRepositoryProvider,
|
||||
(previous, next) {},
|
||||
);
|
||||
|
||||
ref.listenManual(
|
||||
engineSettingsReplicationServiceProvider,
|
||||
(previous, next) {},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -124,6 +149,10 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
_periodicScreenshotUpdate?.cancel();
|
||||
_timerPaused = true;
|
||||
}
|
||||
|
||||
ref
|
||||
.read(localAuthenticationServiceProvider.notifier)
|
||||
.evictCacheOnBackground();
|
||||
case AppLifecycleState.resumed:
|
||||
if (_timerPaused) {
|
||||
_periodicScreenshotUpdate =
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ class LandingAction extends HookConsumerWidget {
|
||||
final sessionTokenAvailable = false;
|
||||
|
||||
// ref.watch(
|
||||
// settingsRepositoryProvider.select(
|
||||
// generalSettingsRepositoryProvider.select(
|
||||
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
|
||||
// ),
|
||||
// );
|
||||
|
||||
+3
-3
@@ -2,6 +2,7 @@ import 'package:expandable_page_view/expandable_page_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/extensions/nullable.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/summarize_tab.dart';
|
||||
@@ -24,9 +25,8 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sharedContent = useMemoized(
|
||||
() => (parameter.content != null)
|
||||
? SharedContent.parse(parameter.content!)
|
||||
: null,
|
||||
() => parameter.content
|
||||
.mapNotNull((content) => SharedContent.parse(content)),
|
||||
[parameter],
|
||||
);
|
||||
|
||||
|
||||
+11
-11
@@ -56,15 +56,15 @@ class _TabDraggable extends HookConsumerWidget {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onDoubleTap: () {
|
||||
ref.read(overlayDialogControllerProvider.notifier).show(
|
||||
TabActionDialog(
|
||||
initialTab: tab,
|
||||
onDismiss:
|
||||
ref.read(overlayDialogControllerProvider.notifier).dismiss,
|
||||
),
|
||||
);
|
||||
},
|
||||
// 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);
|
||||
},
|
||||
@@ -178,8 +178,8 @@ class _Tab extends HookConsumerWidget {
|
||||
|
||||
return ContainerChips(
|
||||
selectedContainer: selectedContainer,
|
||||
onSelected: (container) {
|
||||
ref
|
||||
onSelected: (container) async {
|
||||
await ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@ class PreferenceSetting with FastEquatable {
|
||||
|
||||
bool get isActive => value == actualValue;
|
||||
|
||||
final String title;
|
||||
final String? title;
|
||||
final String? description;
|
||||
|
||||
final bool requireUserOptIn;
|
||||
|
||||
+5
-5
@@ -73,7 +73,7 @@ extension $PreferenceSettingGroupCopyWith on PreferenceSettingGroup {
|
||||
abstract class _$PreferenceSettingCWProxy {
|
||||
PreferenceSetting value(Object value);
|
||||
|
||||
PreferenceSetting title(String title);
|
||||
PreferenceSetting title(String? title);
|
||||
|
||||
PreferenceSetting description(String? description);
|
||||
|
||||
@@ -91,7 +91,7 @@ abstract class _$PreferenceSettingCWProxy {
|
||||
/// ````
|
||||
PreferenceSetting call({
|
||||
Object value,
|
||||
String title,
|
||||
String? title,
|
||||
String? description,
|
||||
Object? actualValue,
|
||||
bool requireUserOptIn,
|
||||
@@ -109,7 +109,7 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
|
||||
PreferenceSetting value(Object value) => this(value: value);
|
||||
|
||||
@override
|
||||
PreferenceSetting title(String title) => this(title: title);
|
||||
PreferenceSetting title(String? title) => this(title: title);
|
||||
|
||||
@override
|
||||
PreferenceSetting description(String? description) =>
|
||||
@@ -151,7 +151,7 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
|
||||
title: title == const $CopyWithPlaceholder()
|
||||
? _value.title
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: title as String,
|
||||
: title as String?,
|
||||
description: description == const $CopyWithPlaceholder()
|
||||
? _value.description
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
@@ -186,7 +186,7 @@ extension $PreferenceSettingCopyWith on PreferenceSetting {
|
||||
PreferenceSetting _$PreferenceSettingFromJson(Map<String, dynamic> json) =>
|
||||
PreferenceSetting(
|
||||
value: json['value'] as Object,
|
||||
title: json['title'] as String,
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
requireUserOptIn: json['requireUserOptIn'] as bool? ?? false,
|
||||
shouldBeDefault: json['shouldBeDefault'] as bool? ?? false,
|
||||
|
||||
+49
-41
@@ -5,6 +5,7 @@ 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:lensai/features/geckoview/features/tabs/utils/setting_groups_serializer.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
@@ -12,40 +13,29 @@ 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
|
||||
Future<Map<String, dynamic>> _preferenceSettingContent(Ref ref) async {
|
||||
return 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<Map<String, PreferenceSettingGroup>> _preferenceSettingGroups(
|
||||
Ref ref,
|
||||
PreferencePartition partition,
|
||||
) async {
|
||||
final content = await ref.read(_preferenceSettingContentProvider.future);
|
||||
return deserializePreferenceSettingGroups(partition, content);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<PreferenceSettingGroup> _preferenceSettingGroup(
|
||||
Ref ref,
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
) {
|
||||
return ref.watch(
|
||||
_preferenceSettingGroupsProvider.selectAsync(
|
||||
_preferenceSettingGroupsProvider(partition).selectAsync(
|
||||
(groups) =>
|
||||
groups[groupName] ?? (throw Exception('Unknown setting group')),
|
||||
),
|
||||
@@ -90,13 +80,16 @@ class _PreferenceRepository extends _$PreferenceRepository {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
class PreferenceSettingsGeneralRepository
|
||||
extends _$PreferenceSettingsGeneralRepository {
|
||||
late Map<String, PreferenceSettingGroup> _statelessGroups;
|
||||
class UnifiedPreferenceSettingsRepository
|
||||
extends _$UnifiedPreferenceSettingsRepository {
|
||||
Map<String, PreferenceSettingGroup>? _statelessGroups;
|
||||
|
||||
Future<void> apply() async {
|
||||
_statelessGroups =
|
||||
await ref.read(_preferenceSettingGroupsProvider(partition).future);
|
||||
|
||||
final prefs = {
|
||||
for (final group in _statelessGroups.values)
|
||||
for (final group in _statelessGroups!.values)
|
||||
...Map.fromEntries(
|
||||
group.settings.entries
|
||||
.where((e) => !e.value.requireUserOptIn)
|
||||
@@ -108,7 +101,10 @@ class PreferenceSettingsGeneralRepository
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
final prefs = _statelessGroups.values
|
||||
_statelessGroups =
|
||||
await ref.read(_preferenceSettingGroupsProvider(partition).future);
|
||||
|
||||
final prefs = _statelessGroups!.values
|
||||
.map((group) => group.settings.keys)
|
||||
.flattened
|
||||
.toList();
|
||||
@@ -117,13 +113,16 @@ class PreferenceSettingsGeneralRepository
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Map<String, PreferenceSettingGroup>> build() async* {
|
||||
_statelessGroups = await ref.watch(_preferenceSettingGroupsProvider.future);
|
||||
Stream<Map<String, PreferenceSettingGroup>> build(
|
||||
PreferencePartition partition,
|
||||
) async* {
|
||||
_statelessGroups =
|
||||
await ref.watch(_preferenceSettingGroupsProvider(partition).future);
|
||||
|
||||
final prefStream = ref.watch(_preferenceRepositoryProvider);
|
||||
|
||||
yield* prefStream.map(
|
||||
(prefs) => _statelessGroups.map(
|
||||
(prefs) => _statelessGroups!.map(
|
||||
(groupName, group) => MapEntry(
|
||||
groupName,
|
||||
group.copyWith.settings(
|
||||
@@ -143,18 +142,21 @@ class PreferenceSettingsGeneralRepository
|
||||
@Riverpod()
|
||||
class PreferenceSettingsGroupRepository
|
||||
extends _$PreferenceSettingsGroupRepository {
|
||||
late PreferenceSettingGroup _statelessSettingGroup;
|
||||
PreferenceSettingGroup? _statelessSettingGroup;
|
||||
|
||||
Future<void> apply({List<String>? filter}) async {
|
||||
_statelessSettingGroup ??= await ref
|
||||
.read(_preferenceSettingGroupProvider(partition, groupName).future);
|
||||
|
||||
final prefs = Map.fromEntries(
|
||||
filter?.map(
|
||||
(e) => MapEntry(
|
||||
e,
|
||||
_statelessSettingGroup.settings[e]?.value ??
|
||||
_statelessSettingGroup!.settings[e]?.value ??
|
||||
(throw Exception('Preference not part of group')),
|
||||
),
|
||||
) ??
|
||||
_statelessSettingGroup.settings.entries
|
||||
_statelessSettingGroup!.settings.entries
|
||||
.where((e) => !e.value.requireUserOptIn)
|
||||
.map((e) => MapEntry(e.key, e.value.value)),
|
||||
);
|
||||
@@ -163,28 +165,34 @@ class PreferenceSettingsGroupRepository
|
||||
}
|
||||
|
||||
Future<void> reset({List<String>? filter}) async {
|
||||
_statelessSettingGroup ??= await ref
|
||||
.read(_preferenceSettingGroupProvider(partition, groupName).future);
|
||||
|
||||
if (filter != null &&
|
||||
filter.any(
|
||||
(value) => !_statelessSettingGroup.settings.containsKey(value),
|
||||
(value) => !_statelessSettingGroup!.settings.containsKey(value),
|
||||
)) {
|
||||
throw Exception('Preference not part of group');
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(_preferenceRepositoryProvider.notifier)
|
||||
.resetPrefs(filter ?? _statelessSettingGroup.settings.keys.toList());
|
||||
.resetPrefs(filter ?? _statelessSettingGroup!.settings.keys.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<PreferenceSettingGroup> build(String groupName) async* {
|
||||
_statelessSettingGroup =
|
||||
await ref.watch(_preferenceSettingGroupProvider(groupName).future);
|
||||
Stream<PreferenceSettingGroup> build(
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
) async* {
|
||||
_statelessSettingGroup = await ref
|
||||
.watch(_preferenceSettingGroupProvider(partition, groupName).future);
|
||||
|
||||
final prefStream = ref.watch(_preferenceRepositoryProvider);
|
||||
|
||||
yield* prefStream.map(
|
||||
(prefs) => _statelessSettingGroup.copyWith.settings(
|
||||
_statelessSettingGroup.settings.map(
|
||||
(prefs) => _statelessSettingGroup!.copyWith.settings(
|
||||
_statelessSettingGroup!.settings.map(
|
||||
(key, value) => MapEntry(key, value.copyWith.actualValue(prefs[key])),
|
||||
),
|
||||
),
|
||||
|
||||
+350
-33
@@ -6,28 +6,27 @@ part of 'preference_settings.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$preferenceSettingGroupsHash() =>
|
||||
r'fa9286248cc71c88c888a518441721dc99b7e0c2';
|
||||
String _$preferenceSettingContentHash() =>
|
||||
r'a8a61fdf8800cb7adb365c100409ddce09c574b1';
|
||||
|
||||
/// See also [_preferenceSettingGroups].
|
||||
@ProviderFor(_preferenceSettingGroups)
|
||||
final _preferenceSettingGroupsProvider =
|
||||
FutureProvider<Map<String, PreferenceSettingGroup>>.internal(
|
||||
_preferenceSettingGroups,
|
||||
name: r'_preferenceSettingGroupsProvider',
|
||||
/// See also [_preferenceSettingContent].
|
||||
@ProviderFor(_preferenceSettingContent)
|
||||
final _preferenceSettingContentProvider =
|
||||
FutureProvider<Map<String, dynamic>>.internal(
|
||||
_preferenceSettingContent,
|
||||
name: r'_preferenceSettingContentProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceSettingGroupsHash,
|
||||
: _$preferenceSettingContentHash,
|
||||
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';
|
||||
typedef _PreferenceSettingContentRef = FutureProviderRef<Map<String, dynamic>>;
|
||||
String _$preferenceSettingGroupsHash() =>
|
||||
r'd267dacf82a11568c9cf0cc864f434db35f93394';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -50,6 +49,145 @@ class _SystemHash {
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [_preferenceSettingGroups].
|
||||
@ProviderFor(_preferenceSettingGroups)
|
||||
const _preferenceSettingGroupsProvider = _PreferenceSettingGroupsFamily();
|
||||
|
||||
/// See also [_preferenceSettingGroups].
|
||||
class _PreferenceSettingGroupsFamily
|
||||
extends Family<AsyncValue<Map<String, PreferenceSettingGroup>>> {
|
||||
/// See also [_preferenceSettingGroups].
|
||||
const _PreferenceSettingGroupsFamily();
|
||||
|
||||
/// See also [_preferenceSettingGroups].
|
||||
_PreferenceSettingGroupsProvider call(
|
||||
PreferencePartition partition,
|
||||
) {
|
||||
return _PreferenceSettingGroupsProvider(
|
||||
partition,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
_PreferenceSettingGroupsProvider getProviderOverride(
|
||||
covariant _PreferenceSettingGroupsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.partition,
|
||||
);
|
||||
}
|
||||
|
||||
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'_preferenceSettingGroupsProvider';
|
||||
}
|
||||
|
||||
/// See also [_preferenceSettingGroups].
|
||||
class _PreferenceSettingGroupsProvider
|
||||
extends FutureProvider<Map<String, PreferenceSettingGroup>> {
|
||||
/// See also [_preferenceSettingGroups].
|
||||
_PreferenceSettingGroupsProvider(
|
||||
PreferencePartition partition,
|
||||
) : this._internal(
|
||||
(ref) => _preferenceSettingGroups(
|
||||
ref as _PreferenceSettingGroupsRef,
|
||||
partition,
|
||||
),
|
||||
from: _preferenceSettingGroupsProvider,
|
||||
name: r'_preferenceSettingGroupsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$preferenceSettingGroupsHash,
|
||||
dependencies: _PreferenceSettingGroupsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
_PreferenceSettingGroupsFamily._allTransitiveDependencies,
|
||||
partition: partition,
|
||||
);
|
||||
|
||||
_PreferenceSettingGroupsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.partition,
|
||||
}) : super.internal();
|
||||
|
||||
final PreferencePartition partition;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<Map<String, PreferenceSettingGroup>> Function(
|
||||
_PreferenceSettingGroupsRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: _PreferenceSettingGroupsProvider._internal(
|
||||
(ref) => create(ref as _PreferenceSettingGroupsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
partition: partition,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureProviderElement<Map<String, PreferenceSettingGroup>> createElement() {
|
||||
return _PreferenceSettingGroupsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is _PreferenceSettingGroupsProvider &&
|
||||
other.partition == partition;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, partition.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin _PreferenceSettingGroupsRef
|
||||
on FutureProviderRef<Map<String, PreferenceSettingGroup>> {
|
||||
/// The parameter `partition` of this provider.
|
||||
PreferencePartition get partition;
|
||||
}
|
||||
|
||||
class _PreferenceSettingGroupsProviderElement
|
||||
extends FutureProviderElement<Map<String, PreferenceSettingGroup>>
|
||||
with _PreferenceSettingGroupsRef {
|
||||
_PreferenceSettingGroupsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
PreferencePartition get partition =>
|
||||
(origin as _PreferenceSettingGroupsProvider).partition;
|
||||
}
|
||||
|
||||
String _$preferenceSettingGroupHash() =>
|
||||
r'64d80a37928d7e5aea7d3d567dde8ade7292cd5f';
|
||||
|
||||
/// See also [_preferenceSettingGroup].
|
||||
@ProviderFor(_preferenceSettingGroup)
|
||||
const _preferenceSettingGroupProvider = _PreferenceSettingGroupFamily();
|
||||
@@ -62,9 +200,11 @@ class _PreferenceSettingGroupFamily
|
||||
|
||||
/// See also [_preferenceSettingGroup].
|
||||
_PreferenceSettingGroupProvider call(
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
) {
|
||||
return _PreferenceSettingGroupProvider(
|
||||
partition,
|
||||
groupName,
|
||||
);
|
||||
}
|
||||
@@ -74,6 +214,7 @@ class _PreferenceSettingGroupFamily
|
||||
covariant _PreferenceSettingGroupProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.partition,
|
||||
provider.groupName,
|
||||
);
|
||||
}
|
||||
@@ -98,10 +239,12 @@ class _PreferenceSettingGroupProvider
|
||||
extends FutureProvider<PreferenceSettingGroup> {
|
||||
/// See also [_preferenceSettingGroup].
|
||||
_PreferenceSettingGroupProvider(
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
) : this._internal(
|
||||
(ref) => _preferenceSettingGroup(
|
||||
ref as _PreferenceSettingGroupRef,
|
||||
partition,
|
||||
groupName,
|
||||
),
|
||||
from: _preferenceSettingGroupProvider,
|
||||
@@ -113,6 +256,7 @@ class _PreferenceSettingGroupProvider
|
||||
dependencies: _PreferenceSettingGroupFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
_PreferenceSettingGroupFamily._allTransitiveDependencies,
|
||||
partition: partition,
|
||||
groupName: groupName,
|
||||
);
|
||||
|
||||
@@ -123,9 +267,11 @@ class _PreferenceSettingGroupProvider
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.partition,
|
||||
required this.groupName,
|
||||
}) : super.internal();
|
||||
|
||||
final PreferencePartition partition;
|
||||
final String groupName;
|
||||
|
||||
@override
|
||||
@@ -143,6 +289,7 @@ class _PreferenceSettingGroupProvider
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
partition: partition,
|
||||
groupName: groupName,
|
||||
),
|
||||
);
|
||||
@@ -156,12 +303,14 @@ class _PreferenceSettingGroupProvider
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is _PreferenceSettingGroupProvider &&
|
||||
other.partition == partition &&
|
||||
other.groupName == groupName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, partition.hashCode);
|
||||
hash = _SystemHash.combine(hash, groupName.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
@@ -171,6 +320,9 @@ class _PreferenceSettingGroupProvider
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin _PreferenceSettingGroupRef on FutureProviderRef<PreferenceSettingGroup> {
|
||||
/// The parameter `partition` of this provider.
|
||||
PreferencePartition get partition;
|
||||
|
||||
/// The parameter `groupName` of this provider.
|
||||
String get groupName;
|
||||
}
|
||||
@@ -180,6 +332,9 @@ class _PreferenceSettingGroupProviderElement
|
||||
with _PreferenceSettingGroupRef {
|
||||
_PreferenceSettingGroupProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
PreferencePartition get partition =>
|
||||
(origin as _PreferenceSettingGroupProvider).partition;
|
||||
@override
|
||||
String get groupName => (origin as _PreferenceSettingGroupProvider).groupName;
|
||||
}
|
||||
@@ -202,33 +357,174 @@ final _preferenceRepositoryProvider = AutoDisposeNotifierProvider<
|
||||
|
||||
typedef _$PreferenceRepository
|
||||
= AutoDisposeNotifier<Raw<Stream<Map<String, Object>>>>;
|
||||
String _$preferenceSettingsGeneralRepositoryHash() =>
|
||||
r'24a70de8284107bb43778e1014abe41bd805987a';
|
||||
String _$unifiedPreferenceSettingsRepositoryHash() =>
|
||||
r'35aa16ffa7aa1453c8331ed7470ef85a68d27178';
|
||||
|
||||
/// 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,
|
||||
);
|
||||
abstract class _$UnifiedPreferenceSettingsRepository
|
||||
extends BuildlessAutoDisposeStreamNotifier<
|
||||
Map<String, PreferenceSettingGroup>> {
|
||||
late final PreferencePartition partition;
|
||||
|
||||
Stream<Map<String, PreferenceSettingGroup>> build(
|
||||
PreferencePartition partition,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [UnifiedPreferenceSettingsRepository].
|
||||
@ProviderFor(UnifiedPreferenceSettingsRepository)
|
||||
const unifiedPreferenceSettingsRepositoryProvider =
|
||||
UnifiedPreferenceSettingsRepositoryFamily();
|
||||
|
||||
/// See also [UnifiedPreferenceSettingsRepository].
|
||||
class UnifiedPreferenceSettingsRepositoryFamily
|
||||
extends Family<AsyncValue<Map<String, PreferenceSettingGroup>>> {
|
||||
/// See also [UnifiedPreferenceSettingsRepository].
|
||||
const UnifiedPreferenceSettingsRepositoryFamily();
|
||||
|
||||
/// See also [UnifiedPreferenceSettingsRepository].
|
||||
UnifiedPreferenceSettingsRepositoryProvider call(
|
||||
PreferencePartition partition,
|
||||
) {
|
||||
return UnifiedPreferenceSettingsRepositoryProvider(
|
||||
partition,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
UnifiedPreferenceSettingsRepositoryProvider getProviderOverride(
|
||||
covariant UnifiedPreferenceSettingsRepositoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.partition,
|
||||
);
|
||||
}
|
||||
|
||||
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'unifiedPreferenceSettingsRepositoryProvider';
|
||||
}
|
||||
|
||||
/// See also [UnifiedPreferenceSettingsRepository].
|
||||
class UnifiedPreferenceSettingsRepositoryProvider
|
||||
extends AutoDisposeStreamNotifierProviderImpl<
|
||||
UnifiedPreferenceSettingsRepository,
|
||||
Map<String, PreferenceSettingGroup>> {
|
||||
/// See also [UnifiedPreferenceSettingsRepository].
|
||||
UnifiedPreferenceSettingsRepositoryProvider(
|
||||
PreferencePartition partition,
|
||||
) : this._internal(
|
||||
() => UnifiedPreferenceSettingsRepository()..partition = partition,
|
||||
from: unifiedPreferenceSettingsRepositoryProvider,
|
||||
name: r'unifiedPreferenceSettingsRepositoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$unifiedPreferenceSettingsRepositoryHash,
|
||||
dependencies: UnifiedPreferenceSettingsRepositoryFamily._dependencies,
|
||||
allTransitiveDependencies: UnifiedPreferenceSettingsRepositoryFamily
|
||||
._allTransitiveDependencies,
|
||||
partition: partition,
|
||||
);
|
||||
|
||||
UnifiedPreferenceSettingsRepositoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.partition,
|
||||
}) : super.internal();
|
||||
|
||||
final PreferencePartition partition;
|
||||
|
||||
@override
|
||||
Stream<Map<String, PreferenceSettingGroup>> runNotifierBuild(
|
||||
covariant UnifiedPreferenceSettingsRepository notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
partition,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(UnifiedPreferenceSettingsRepository Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: UnifiedPreferenceSettingsRepositoryProvider._internal(
|
||||
() => create()..partition = partition,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
partition: partition,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamNotifierProviderElement<UnifiedPreferenceSettingsRepository,
|
||||
Map<String, PreferenceSettingGroup>> createElement() {
|
||||
return _UnifiedPreferenceSettingsRepositoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is UnifiedPreferenceSettingsRepositoryProvider &&
|
||||
other.partition == partition;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, partition.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin UnifiedPreferenceSettingsRepositoryRef
|
||||
on AutoDisposeStreamNotifierProviderRef<
|
||||
Map<String, PreferenceSettingGroup>> {
|
||||
/// The parameter `partition` of this provider.
|
||||
PreferencePartition get partition;
|
||||
}
|
||||
|
||||
class _UnifiedPreferenceSettingsRepositoryProviderElement
|
||||
extends AutoDisposeStreamNotifierProviderElement<
|
||||
UnifiedPreferenceSettingsRepository,
|
||||
Map<String, PreferenceSettingGroup>>
|
||||
with UnifiedPreferenceSettingsRepositoryRef {
|
||||
_UnifiedPreferenceSettingsRepositoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
PreferencePartition get partition =>
|
||||
(origin as UnifiedPreferenceSettingsRepositoryProvider).partition;
|
||||
}
|
||||
|
||||
typedef _$PreferenceSettingsGeneralRepository
|
||||
= AutoDisposeStreamNotifier<Map<String, PreferenceSettingGroup>>;
|
||||
String _$preferenceSettingsGroupRepositoryHash() =>
|
||||
r'023794294c5639ac6c6941f87a4ed91e1551fbc9';
|
||||
r'60f416ffe1cde4d535fb5b643a80aead20836bae';
|
||||
|
||||
abstract class _$PreferenceSettingsGroupRepository
|
||||
extends BuildlessAutoDisposeStreamNotifier<PreferenceSettingGroup> {
|
||||
late final PreferencePartition partition;
|
||||
late final String groupName;
|
||||
|
||||
Stream<PreferenceSettingGroup> build(
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
);
|
||||
}
|
||||
@@ -246,9 +542,11 @@ class PreferenceSettingsGroupRepositoryFamily
|
||||
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
PreferenceSettingsGroupRepositoryProvider call(
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
) {
|
||||
return PreferenceSettingsGroupRepositoryProvider(
|
||||
partition,
|
||||
groupName,
|
||||
);
|
||||
}
|
||||
@@ -258,6 +556,7 @@ class PreferenceSettingsGroupRepositoryFamily
|
||||
covariant PreferenceSettingsGroupRepositoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.partition,
|
||||
provider.groupName,
|
||||
);
|
||||
}
|
||||
@@ -283,9 +582,12 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
PreferenceSettingsGroupRepository, PreferenceSettingGroup> {
|
||||
/// See also [PreferenceSettingsGroupRepository].
|
||||
PreferenceSettingsGroupRepositoryProvider(
|
||||
PreferencePartition partition,
|
||||
String groupName,
|
||||
) : this._internal(
|
||||
() => PreferenceSettingsGroupRepository()..groupName = groupName,
|
||||
() => PreferenceSettingsGroupRepository()
|
||||
..partition = partition
|
||||
..groupName = groupName,
|
||||
from: preferenceSettingsGroupRepositoryProvider,
|
||||
name: r'preferenceSettingsGroupRepositoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -295,6 +597,7 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
dependencies: PreferenceSettingsGroupRepositoryFamily._dependencies,
|
||||
allTransitiveDependencies: PreferenceSettingsGroupRepositoryFamily
|
||||
._allTransitiveDependencies,
|
||||
partition: partition,
|
||||
groupName: groupName,
|
||||
);
|
||||
|
||||
@@ -305,9 +608,11 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.partition,
|
||||
required this.groupName,
|
||||
}) : super.internal();
|
||||
|
||||
final PreferencePartition partition;
|
||||
final String groupName;
|
||||
|
||||
@override
|
||||
@@ -315,6 +620,7 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
covariant PreferenceSettingsGroupRepository notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
partition,
|
||||
groupName,
|
||||
);
|
||||
}
|
||||
@@ -324,12 +630,15 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PreferenceSettingsGroupRepositoryProvider._internal(
|
||||
() => create()..groupName = groupName,
|
||||
() => create()
|
||||
..partition = partition
|
||||
..groupName = groupName,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
partition: partition,
|
||||
groupName: groupName,
|
||||
),
|
||||
);
|
||||
@@ -344,12 +653,14 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PreferenceSettingsGroupRepositoryProvider &&
|
||||
other.partition == partition &&
|
||||
other.groupName == groupName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, partition.hashCode);
|
||||
hash = _SystemHash.combine(hash, groupName.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
@@ -360,6 +671,9 @@ class PreferenceSettingsGroupRepositoryProvider
|
||||
// ignore: unused_element
|
||||
mixin PreferenceSettingsGroupRepositoryRef
|
||||
on AutoDisposeStreamNotifierProviderRef<PreferenceSettingGroup> {
|
||||
/// The parameter `partition` of this provider.
|
||||
PreferencePartition get partition;
|
||||
|
||||
/// The parameter `groupName` of this provider.
|
||||
String get groupName;
|
||||
}
|
||||
@@ -370,6 +684,9 @@ class _PreferenceSettingsGroupRepositoryProviderElement
|
||||
PreferenceSettingGroup> with PreferenceSettingsGroupRepositoryRef {
|
||||
_PreferenceSettingsGroupRepositoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
PreferencePartition get partition =>
|
||||
(origin as PreferenceSettingsGroupRepositoryProvider).partition;
|
||||
@override
|
||||
String get groupName =>
|
||||
(origin as PreferenceSettingsGroupRepositoryProvider).groupName;
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
|
||||
|
||||
class ReaderButton extends HookConsumerWidget {
|
||||
@@ -16,7 +16,8 @@ class ReaderButton extends HookConsumerWidget {
|
||||
final readerChanging = ref.watch(readerableScreenControllerProvider);
|
||||
|
||||
final enableReadability = ref.watch(
|
||||
settingsRepositoryProvider.select((value) => value.enableReadability),
|
||||
generalSettingsRepositoryProvider
|
||||
.select((value) => value.enableReadability),
|
||||
);
|
||||
|
||||
final readerabilityState = ref.watch(
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
class ContainerMetadataConverter
|
||||
implements TypeConverter<ContainerMetadata, String> {
|
||||
const ContainerMetadataConverter();
|
||||
|
||||
@override
|
||||
ContainerMetadata fromSql(String fromDb) {
|
||||
final json = jsonDecode(fromDb) as Map<String, dynamic>;
|
||||
return ContainerMetadata.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(ContainerMetadata value) {
|
||||
return jsonEncode(value.toJson());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/core/uuid.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
@@ -12,26 +11,24 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
|
||||
with _$ContainerDaoMixin {
|
||||
ContainerDao(super.db);
|
||||
|
||||
Future<void> addContainer({String? name, required Color color}) {
|
||||
Future<void> addContainer(ContainerData container) {
|
||||
return db.container.insertOne(
|
||||
ContainerCompanion.insert(
|
||||
id: uuid.v7(),
|
||||
name: Value(name),
|
||||
color: color,
|
||||
id: container.id,
|
||||
name: Value(container.name),
|
||||
color: container.color,
|
||||
metadata: Value(container.metadata),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> replaceContainer(
|
||||
String id, {
|
||||
required String? name,
|
||||
required Color color,
|
||||
}) {
|
||||
Future<void> replaceContainer(ContainerData container) {
|
||||
return db.container.replaceOne(
|
||||
ContainerCompanion(
|
||||
id: Value(id),
|
||||
name: Value(name),
|
||||
color: Value(color),
|
||||
id: Value(container.id),
|
||||
name: Value(container.name),
|
||||
color: Value(container.color),
|
||||
metadata: Value(container.metadata),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
||||
return query.map((row) => row.read(db.tab.id)!);
|
||||
}
|
||||
|
||||
SingleSelectable<String?> tabContainerId(String tabId) {
|
||||
SingleOrNullSelectable<String?> tabContainerId(String tabId) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.containerId])
|
||||
..where(db.tab.id.equals(tabId));
|
||||
@@ -49,7 +49,41 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
||||
return query.map((row) => row.read(db.tab.containerId));
|
||||
}
|
||||
|
||||
Future<String> upsertTab(
|
||||
Future<String> upsertContainerTabTransactional(
|
||||
Future<String> Function() createTab, {
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
Value<String?> orderKey = const Value.absent(),
|
||||
}) {
|
||||
return db.transaction(() async {
|
||||
final tabId = await createTab();
|
||||
|
||||
final currentOrderKey = orderKey.value ??
|
||||
await db.containerDao
|
||||
.generateLeadingOrderKey(containerId.value)
|
||||
.getSingle();
|
||||
|
||||
await db.tab.insertOne(
|
||||
TabCompanion.insert(
|
||||
id: tabId,
|
||||
timestamp: DateTime.now(),
|
||||
containerId: containerId,
|
||||
orderKey: currentOrderKey,
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(old) => TabCompanion.custom(
|
||||
containerId:
|
||||
(containerId.present) ? Variable(containerId.value) : null,
|
||||
orderKey: (orderKey.present) ? Variable(orderKey.value) : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return tabId;
|
||||
});
|
||||
}
|
||||
|
||||
//Upsert an tab only if there is no container assigned yet
|
||||
Future<String> upsertUnassignedTab(
|
||||
String tabId, {
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
Value<String?> orderKey = const Value.absent(),
|
||||
@@ -73,6 +107,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
||||
(containerId.present) ? Variable(containerId.value) : null,
|
||||
orderKey: (orderKey.present) ? Variable(orderKey.value) : null,
|
||||
),
|
||||
where: (old) => old.containerId.isNull(),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/widgets.dart' show Color, IconData;
|
||||
import 'package:flutter/widgets.dart' show Color;
|
||||
import 'package:lensai/data/database/converters/color.dart';
|
||||
import 'package:lensai/data/database/converters/icon_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import 'package:lensai/data/database/converters/color.dart';
|
||||
import 'package:lensai/data/database/converters/icon_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart';
|
||||
|
||||
CREATE TABLE container (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
contextual_identity TEXT,
|
||||
name TEXT,
|
||||
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`,
|
||||
icon TEXT MAPPED BY `const IconDataTypeConverter()`
|
||||
metadata TEXT MAPPED BY `const ContainerMetadataConverter()`
|
||||
) WITH ContainerData;
|
||||
|
||||
CREATE TABLE tab (
|
||||
@@ -79,7 +78,7 @@ containersWithCount WITH ContainerDataWithCount:
|
||||
FROM tab
|
||||
GROUP BY container_id
|
||||
) AS tab_agg ON container.id = tab_agg.container_id
|
||||
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
|
||||
ORDER BY tab_agg.last_updated DESC NULLS LAST;
|
||||
|
||||
leadingOrderKey(:container_id AS TEXT OR NULL, :bucket AS INTEGER):
|
||||
SELECT lexo_rank_previous(
|
||||
|
||||
@@ -13,11 +13,6 @@ class Container extends Table with TableInfo<Container, ContainerData> {
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> contextualIdentity =
|
||||
GeneratedColumn<String>('contextual_identity', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
@@ -29,15 +24,14 @@ class Container extends Table with TableInfo<Container, ContainerData> {
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL')
|
||||
.withConverter<Color>(Container.$convertercolor);
|
||||
late final GeneratedColumnWithTypeConverter<IconData?, String> icon =
|
||||
GeneratedColumn<String>('icon', aliasedName, true,
|
||||
late final GeneratedColumnWithTypeConverter<ContainerMetadata?, String>
|
||||
metadata = GeneratedColumn<String>('metadata', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '')
|
||||
.withConverter<IconData?>(Container.$convertericonn);
|
||||
.withConverter<ContainerMetadata?>(Container.$convertermetadatan);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[id, contextualIdentity, name, color, icon];
|
||||
List<GeneratedColumn> get $columns => [id, name, color, metadata];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
@@ -51,14 +45,13 @@ class Container extends Table with TableInfo<Container, ContainerData> {
|
||||
return ContainerData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
contextualIdentity: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string, data['${effectivePrefix}contextual_identity']),
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}name']),
|
||||
color: Container.$convertercolor.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}color'])!),
|
||||
icon: Container.$convertericonn.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}icon'])),
|
||||
metadata: Container.$convertermetadatan.fromSql(attachedDatabase
|
||||
.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}metadata'])),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,69 +61,62 @@ class Container extends Table with TableInfo<Container, ContainerData> {
|
||||
}
|
||||
|
||||
static TypeConverter<Color, int> $convertercolor = const ColorConverter();
|
||||
static TypeConverter<IconData, String> $convertericon =
|
||||
const IconDataTypeConverter();
|
||||
static TypeConverter<IconData?, String?> $convertericonn =
|
||||
NullAwareTypeConverter.wrap($convertericon);
|
||||
static TypeConverter<ContainerMetadata, String> $convertermetadata =
|
||||
const ContainerMetadataConverter();
|
||||
static TypeConverter<ContainerMetadata?, String?> $convertermetadatan =
|
||||
NullAwareTypeConverter.wrap($convertermetadata);
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class ContainerCompanion extends UpdateCompanion<ContainerData> {
|
||||
final Value<String> id;
|
||||
final Value<String?> contextualIdentity;
|
||||
final Value<String?> name;
|
||||
final Value<Color> color;
|
||||
final Value<IconData?> icon;
|
||||
final Value<ContainerMetadata?> metadata;
|
||||
final Value<int> rowid;
|
||||
const ContainerCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.contextualIdentity = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
this.color = const Value.absent(),
|
||||
this.icon = const Value.absent(),
|
||||
this.metadata = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
ContainerCompanion.insert({
|
||||
required String id,
|
||||
this.contextualIdentity = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
required Color color,
|
||||
this.icon = const Value.absent(),
|
||||
this.metadata = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
color = Value(color);
|
||||
static Insertable<ContainerData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? contextualIdentity,
|
||||
Expression<String>? name,
|
||||
Expression<int>? color,
|
||||
Expression<String>? icon,
|
||||
Expression<String>? metadata,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (contextualIdentity != null) 'contextual_identity': contextualIdentity,
|
||||
if (name != null) 'name': name,
|
||||
if (color != null) 'color': color,
|
||||
if (icon != null) 'icon': icon,
|
||||
if (metadata != null) 'metadata': metadata,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
ContainerCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String?>? contextualIdentity,
|
||||
Value<String?>? name,
|
||||
Value<Color>? color,
|
||||
Value<IconData?>? icon,
|
||||
Value<ContainerMetadata?>? metadata,
|
||||
Value<int>? rowid}) {
|
||||
return ContainerCompanion(
|
||||
id: id ?? this.id,
|
||||
contextualIdentity: contextualIdentity ?? this.contextualIdentity,
|
||||
name: name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
icon: icon ?? this.icon,
|
||||
metadata: metadata ?? this.metadata,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
@@ -141,9 +127,6 @@ class ContainerCompanion extends UpdateCompanion<ContainerData> {
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (contextualIdentity.present) {
|
||||
map['contextual_identity'] = Variable<String>(contextualIdentity.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = Variable<String>(name.value);
|
||||
}
|
||||
@@ -151,9 +134,9 @@ class ContainerCompanion extends UpdateCompanion<ContainerData> {
|
||||
map['color'] =
|
||||
Variable<int>(Container.$convertercolor.toSql(color.value));
|
||||
}
|
||||
if (icon.present) {
|
||||
map['icon'] =
|
||||
Variable<String>(Container.$convertericonn.toSql(icon.value));
|
||||
if (metadata.present) {
|
||||
map['metadata'] =
|
||||
Variable<String>(Container.$convertermetadatan.toSql(metadata.value));
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
@@ -165,10 +148,9 @@ class ContainerCompanion extends UpdateCompanion<ContainerData> {
|
||||
String toString() {
|
||||
return (StringBuffer('ContainerCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('contextualIdentity: $contextualIdentity, ')
|
||||
..write('name: $name, ')
|
||||
..write('color: $color, ')
|
||||
..write('icon: $icon, ')
|
||||
..write('metadata: $metadata, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@@ -1597,18 +1579,18 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
|
||||
Selectable<ContainerDataWithCount> containersWithCount() {
|
||||
return customSelect(
|
||||
'SELECT container.*, tab_agg.tab_count FROM container LEFT JOIN (SELECT container_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY container_id) AS tab_agg ON container.id = tab_agg.container_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
|
||||
'SELECT container.*, tab_agg.tab_count FROM container LEFT JOIN (SELECT container_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY container_id) AS tab_agg ON container.id = tab_agg.container_id ORDER BY tab_agg.last_updated DESC NULLS LAST',
|
||||
variables: [],
|
||||
readsFrom: {
|
||||
container,
|
||||
tab,
|
||||
}).map((QueryRow row) => ContainerDataWithCount(
|
||||
id: row.read<String>('id'),
|
||||
contextualIdentity: row.readNullable<String>('contextual_identity'),
|
||||
name: row.readNullable<String>('name'),
|
||||
color: Container.$convertercolor.fromSql(row.read<int>('color')),
|
||||
icon: NullAwareTypeConverter.wrapFromSql(
|
||||
Container.$convertericon, row.readNullable<String>('icon')),
|
||||
metadata: NullAwareTypeConverter.wrapFromSql(
|
||||
Container.$convertermetadata,
|
||||
row.readNullable<String>('metadata')),
|
||||
tabCount: row.readNullable<int>('tab_count'),
|
||||
));
|
||||
}
|
||||
@@ -1819,18 +1801,16 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
|
||||
typedef $ContainerCreateCompanionBuilder = ContainerCompanion Function({
|
||||
required String id,
|
||||
Value<String?> contextualIdentity,
|
||||
Value<String?> name,
|
||||
required Color color,
|
||||
Value<IconData?> icon,
|
||||
Value<ContainerMetadata?> metadata,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $ContainerUpdateCompanionBuilder = ContainerCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String?> contextualIdentity,
|
||||
Value<String?> name,
|
||||
Value<Color> color,
|
||||
Value<IconData?> icon,
|
||||
Value<ContainerMetadata?> metadata,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
@@ -1864,10 +1844,6 @@ class $ContainerFilterComposer extends Composer<_$TabDatabase, Container> {
|
||||
ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get contextualIdentity => $composableBuilder(
|
||||
column: $table.contextualIdentity,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => ColumnFilters(column));
|
||||
|
||||
@@ -1876,9 +1852,9 @@ class $ContainerFilterComposer extends Composer<_$TabDatabase, Container> {
|
||||
column: $table.color,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<IconData?, IconData, String> get icon =>
|
||||
$composableBuilder(
|
||||
column: $table.icon,
|
||||
ColumnWithTypeConverterFilters<ContainerMetadata?, ContainerMetadata, String>
|
||||
get metadata => $composableBuilder(
|
||||
column: $table.metadata,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
Expression<bool> tabRefs(Expression<bool> Function($TabFilterComposer f) f) {
|
||||
@@ -1913,18 +1889,14 @@ class $ContainerOrderingComposer extends Composer<_$TabDatabase, Container> {
|
||||
ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get contextualIdentity => $composableBuilder(
|
||||
column: $table.contextualIdentity,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get color => $composableBuilder(
|
||||
column: $table.color, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get icon => $composableBuilder(
|
||||
column: $table.icon, builder: (column) => ColumnOrderings(column));
|
||||
ColumnOrderings<String> get metadata => $composableBuilder(
|
||||
column: $table.metadata, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $ContainerAnnotationComposer extends Composer<_$TabDatabase, Container> {
|
||||
@@ -1938,17 +1910,14 @@ class $ContainerAnnotationComposer extends Composer<_$TabDatabase, Container> {
|
||||
GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get contextualIdentity => $composableBuilder(
|
||||
column: $table.contextualIdentity, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<Color, int> get color =>
|
||||
$composableBuilder(column: $table.color, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<IconData?, String> get icon =>
|
||||
$composableBuilder(column: $table.icon, builder: (column) => column);
|
||||
GeneratedColumnWithTypeConverter<ContainerMetadata?, String> get metadata =>
|
||||
$composableBuilder(column: $table.metadata, builder: (column) => column);
|
||||
|
||||
Expression<T> tabRefs<T extends Object>(
|
||||
Expression<T> Function($TabAnnotationComposer a) f) {
|
||||
@@ -1996,34 +1965,30 @@ class $ContainerTableManager extends RootTableManager<
|
||||
$ContainerAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String?> contextualIdentity = const Value.absent(),
|
||||
Value<String?> name = const Value.absent(),
|
||||
Value<Color> color = const Value.absent(),
|
||||
Value<IconData?> icon = const Value.absent(),
|
||||
Value<ContainerMetadata?> metadata = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ContainerCompanion(
|
||||
id: id,
|
||||
contextualIdentity: contextualIdentity,
|
||||
name: name,
|
||||
color: color,
|
||||
icon: icon,
|
||||
metadata: metadata,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
Value<String?> contextualIdentity = const Value.absent(),
|
||||
Value<String?> name = const Value.absent(),
|
||||
required Color color,
|
||||
Value<IconData?> icon = const Value.absent(),
|
||||
Value<ContainerMetadata?> metadata = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ContainerCompanion.insert(
|
||||
id: id,
|
||||
contextualIdentity: contextualIdentity,
|
||||
name: name,
|
||||
color: color,
|
||||
icon: icon,
|
||||
metadata: metadata,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
|
||||
@@ -1,20 +1,103 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:lensai/data/database/converters/icon_data.dart';
|
||||
|
||||
part 'container_data.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable()
|
||||
class ContainerAuthSettings with FastEquatable {
|
||||
final bool authenticationRequired;
|
||||
final bool lockOnAppBackground;
|
||||
final Duration? lockTimeout;
|
||||
|
||||
ContainerAuthSettings({
|
||||
required this.authenticationRequired,
|
||||
required this.lockOnAppBackground,
|
||||
required this.lockTimeout,
|
||||
});
|
||||
|
||||
ContainerAuthSettings.withDefaults({
|
||||
bool? authenticationRequired,
|
||||
bool? lockOnAppBackground,
|
||||
Duration? lockTimeout,
|
||||
}) : this(
|
||||
authenticationRequired: authenticationRequired ?? false,
|
||||
lockOnAppBackground: lockOnAppBackground ?? false,
|
||||
lockTimeout: lockTimeout,
|
||||
);
|
||||
|
||||
factory ContainerAuthSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$ContainerAuthSettingsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ContainerAuthSettingsToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
authenticationRequired,
|
||||
lockOnAppBackground,
|
||||
lockTimeout,
|
||||
];
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
}
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(constructor: 'withDefaults')
|
||||
class ContainerMetadata with FastEquatable {
|
||||
@IconDataJsonConverter()
|
||||
final IconData? iconData;
|
||||
final String? contextualIdentity;
|
||||
final ContainerAuthSettings authSettings;
|
||||
|
||||
ContainerMetadata({
|
||||
required this.iconData,
|
||||
required this.contextualIdentity,
|
||||
required this.authSettings,
|
||||
});
|
||||
|
||||
ContainerMetadata.withDefaults({
|
||||
IconData? iconData,
|
||||
String? contextualIdentity,
|
||||
ContainerAuthSettings? authSettings,
|
||||
}) : this(
|
||||
iconData: iconData,
|
||||
contextualIdentity: contextualIdentity,
|
||||
authSettings: authSettings ?? ContainerAuthSettings.withDefaults(),
|
||||
);
|
||||
|
||||
factory ContainerMetadata.fromJson(Map<String, dynamic> json) =>
|
||||
_$ContainerMetadataFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ContainerMetadataToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
iconData,
|
||||
contextualIdentity,
|
||||
authSettings,
|
||||
];
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
}
|
||||
|
||||
@CopyWith()
|
||||
class ContainerData with FastEquatable {
|
||||
final String id;
|
||||
final String? contextualIdentity;
|
||||
final String? name;
|
||||
final Color color;
|
||||
final IconData? icon;
|
||||
final ContainerMetadata metadata;
|
||||
|
||||
ContainerData({
|
||||
required this.id,
|
||||
this.contextualIdentity,
|
||||
this.name,
|
||||
required this.color,
|
||||
this.icon,
|
||||
});
|
||||
ContainerMetadata? metadata,
|
||||
}) : metadata = metadata ?? ContainerMetadata.withDefaults();
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
@@ -22,10 +105,9 @@ class ContainerData with FastEquatable {
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
id,
|
||||
contextualIdentity,
|
||||
name,
|
||||
color,
|
||||
icon,
|
||||
metadata,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -34,10 +116,9 @@ class ContainerDataWithCount extends ContainerData {
|
||||
|
||||
ContainerDataWithCount({
|
||||
required super.id,
|
||||
super.contextualIdentity,
|
||||
super.name,
|
||||
required super.color,
|
||||
super.icon,
|
||||
super.metadata,
|
||||
required this.tabCount,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'container_data.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$ContainerAuthSettingsCWProxy {
|
||||
ContainerAuthSettings authenticationRequired(bool authenticationRequired);
|
||||
|
||||
ContainerAuthSettings lockOnAppBackground(bool lockOnAppBackground);
|
||||
|
||||
ContainerAuthSettings lockTimeout(Duration? lockTimeout);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerAuthSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// ContainerAuthSettings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
ContainerAuthSettings call({
|
||||
bool authenticationRequired,
|
||||
bool lockOnAppBackground,
|
||||
Duration? lockTimeout,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContainerAuthSettings.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContainerAuthSettings.copyWith.fieldName(...)`
|
||||
class _$ContainerAuthSettingsCWProxyImpl
|
||||
implements _$ContainerAuthSettingsCWProxy {
|
||||
const _$ContainerAuthSettingsCWProxyImpl(this._value);
|
||||
|
||||
final ContainerAuthSettings _value;
|
||||
|
||||
@override
|
||||
ContainerAuthSettings authenticationRequired(bool authenticationRequired) =>
|
||||
this(authenticationRequired: authenticationRequired);
|
||||
|
||||
@override
|
||||
ContainerAuthSettings lockOnAppBackground(bool lockOnAppBackground) =>
|
||||
this(lockOnAppBackground: lockOnAppBackground);
|
||||
|
||||
@override
|
||||
ContainerAuthSettings lockTimeout(Duration? lockTimeout) =>
|
||||
this(lockTimeout: lockTimeout);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerAuthSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// ContainerAuthSettings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
ContainerAuthSettings call({
|
||||
Object? authenticationRequired = const $CopyWithPlaceholder(),
|
||||
Object? lockOnAppBackground = const $CopyWithPlaceholder(),
|
||||
Object? lockTimeout = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return ContainerAuthSettings(
|
||||
authenticationRequired:
|
||||
authenticationRequired == const $CopyWithPlaceholder()
|
||||
? _value.authenticationRequired
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: authenticationRequired as bool,
|
||||
lockOnAppBackground: lockOnAppBackground == const $CopyWithPlaceholder()
|
||||
? _value.lockOnAppBackground
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: lockOnAppBackground as bool,
|
||||
lockTimeout: lockTimeout == const $CopyWithPlaceholder()
|
||||
? _value.lockTimeout
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: lockTimeout as Duration?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $ContainerAuthSettingsCopyWith on ContainerAuthSettings {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfContainerAuthSettings.copyWith(...)` or like so:`instanceOfContainerAuthSettings.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$ContainerAuthSettingsCWProxy get copyWith =>
|
||||
_$ContainerAuthSettingsCWProxyImpl(this);
|
||||
}
|
||||
|
||||
abstract class _$ContainerMetadataCWProxy {
|
||||
ContainerMetadata iconData(IconData? iconData);
|
||||
|
||||
ContainerMetadata contextualIdentity(String? contextualIdentity);
|
||||
|
||||
ContainerMetadata authSettings(ContainerAuthSettings authSettings);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// ContainerMetadata(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
ContainerMetadata call({
|
||||
IconData? iconData,
|
||||
String? contextualIdentity,
|
||||
ContainerAuthSettings authSettings,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContainerMetadata.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContainerMetadata.copyWith.fieldName(...)`
|
||||
class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
||||
const _$ContainerMetadataCWProxyImpl(this._value);
|
||||
|
||||
final ContainerMetadata _value;
|
||||
|
||||
@override
|
||||
ContainerMetadata iconData(IconData? iconData) => this(iconData: iconData);
|
||||
|
||||
@override
|
||||
ContainerMetadata contextualIdentity(String? contextualIdentity) =>
|
||||
this(contextualIdentity: contextualIdentity);
|
||||
|
||||
@override
|
||||
ContainerMetadata authSettings(ContainerAuthSettings authSettings) =>
|
||||
this(authSettings: authSettings);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// ContainerMetadata(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
ContainerMetadata call({
|
||||
Object? iconData = const $CopyWithPlaceholder(),
|
||||
Object? contextualIdentity = const $CopyWithPlaceholder(),
|
||||
Object? authSettings = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return ContainerMetadata(
|
||||
iconData: iconData == const $CopyWithPlaceholder()
|
||||
? _value.iconData
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: iconData as IconData?,
|
||||
contextualIdentity: contextualIdentity == const $CopyWithPlaceholder()
|
||||
? _value.contextualIdentity
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: contextualIdentity as String?,
|
||||
authSettings: authSettings == const $CopyWithPlaceholder()
|
||||
? _value.authSettings
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: authSettings as ContainerAuthSettings,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $ContainerMetadataCopyWith on ContainerMetadata {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfContainerMetadata.copyWith(...)` or like so:`instanceOfContainerMetadata.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$ContainerMetadataCWProxy get copyWith =>
|
||||
_$ContainerMetadataCWProxyImpl(this);
|
||||
}
|
||||
|
||||
abstract class _$ContainerDataCWProxy {
|
||||
ContainerData id(String id);
|
||||
|
||||
ContainerData name(String? name);
|
||||
|
||||
ContainerData color(Color color);
|
||||
|
||||
ContainerData metadata(ContainerMetadata? metadata);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerData(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// ContainerData(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
ContainerData call({
|
||||
String id,
|
||||
String? name,
|
||||
Color color,
|
||||
ContainerMetadata? metadata,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContainerData.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContainerData.copyWith.fieldName(...)`
|
||||
class _$ContainerDataCWProxyImpl implements _$ContainerDataCWProxy {
|
||||
const _$ContainerDataCWProxyImpl(this._value);
|
||||
|
||||
final ContainerData _value;
|
||||
|
||||
@override
|
||||
ContainerData id(String id) => this(id: id);
|
||||
|
||||
@override
|
||||
ContainerData name(String? name) => this(name: name);
|
||||
|
||||
@override
|
||||
ContainerData color(Color color) => this(color: color);
|
||||
|
||||
@override
|
||||
ContainerData metadata(ContainerMetadata? metadata) =>
|
||||
this(metadata: metadata);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerData(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// ContainerData(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
ContainerData call({
|
||||
Object? id = const $CopyWithPlaceholder(),
|
||||
Object? name = const $CopyWithPlaceholder(),
|
||||
Object? color = const $CopyWithPlaceholder(),
|
||||
Object? metadata = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return ContainerData(
|
||||
id: id == const $CopyWithPlaceholder()
|
||||
? _value.id
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: id as String,
|
||||
name: name == const $CopyWithPlaceholder()
|
||||
? _value.name
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: name as String?,
|
||||
color: color == const $CopyWithPlaceholder()
|
||||
? _value.color
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: color as Color,
|
||||
metadata: metadata == const $CopyWithPlaceholder()
|
||||
? _value.metadata
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: metadata as ContainerMetadata?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $ContainerDataCopyWith on ContainerData {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfContainerData.copyWith(...)` or like so:`instanceOfContainerData.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$ContainerDataCWProxy get copyWith => _$ContainerDataCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ContainerAuthSettings _$ContainerAuthSettingsFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ContainerAuthSettings(
|
||||
authenticationRequired: json['authenticationRequired'] as bool,
|
||||
lockOnAppBackground: json['lockOnAppBackground'] as bool,
|
||||
lockTimeout: json['lockTimeout'] == null
|
||||
? null
|
||||
: Duration(microseconds: (json['lockTimeout'] as num).toInt()),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ContainerAuthSettingsToJson(
|
||||
ContainerAuthSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'authenticationRequired': instance.authenticationRequired,
|
||||
'lockOnAppBackground': instance.lockOnAppBackground,
|
||||
'lockTimeout': instance.lockTimeout?.inMicroseconds,
|
||||
};
|
||||
|
||||
ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
|
||||
ContainerMetadata.withDefaults(
|
||||
iconData: _$JsonConverterFromJson<Map<String, dynamic>, IconData>(
|
||||
json['iconData'], const IconDataJsonConverter().fromJson),
|
||||
contextualIdentity: json['contextualIdentity'] as String?,
|
||||
authSettings: json['authSettings'] == null
|
||||
? null
|
||||
: ContainerAuthSettings.fromJson(
|
||||
json['authSettings'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
|
||||
<String, dynamic>{
|
||||
'iconData': _$JsonConverterToJson<Map<String, dynamic>, IconData>(
|
||||
instance.iconData, const IconDataJsonConverter().toJson),
|
||||
'contextualIdentity': instance.contextualIdentity,
|
||||
'authSettings': instance.authSettings,
|
||||
};
|
||||
|
||||
Value? _$JsonConverterFromJson<Json, Value>(
|
||||
Object? json,
|
||||
Value? Function(Json json) fromJson,
|
||||
) =>
|
||||
json == null ? null : fromJson(json as Json);
|
||||
|
||||
Json? _$JsonConverterToJson<Json, Value>(
|
||||
Value? value,
|
||||
Json? Function(Value value) toJson,
|
||||
) =>
|
||||
value == null ? null : toJson(value);
|
||||
@@ -58,12 +58,6 @@ AsyncValue<List<ContainerDataWithCount>> filteredContainersWithCount(
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<String?> tabContainerId(Ref ref, String tabId) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.tabDao.tabContainerId(tabId).watchSingle();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<String>> containerTabIds(
|
||||
Ref ref,
|
||||
|
||||
@@ -206,136 +206,6 @@ class _FilteredContainersWithCountProviderElement
|
||||
(origin as FilteredContainersWithCountProvider).searchText;
|
||||
}
|
||||
|
||||
String _$tabContainerIdHash() => r'726ddd000d23935a2c8ae39c7957412fab16016d';
|
||||
|
||||
/// See also [tabContainerId].
|
||||
@ProviderFor(tabContainerId)
|
||||
const tabContainerIdProvider = TabContainerIdFamily();
|
||||
|
||||
/// See also [tabContainerId].
|
||||
class TabContainerIdFamily extends Family<AsyncValue<String?>> {
|
||||
/// See also [tabContainerId].
|
||||
const TabContainerIdFamily();
|
||||
|
||||
/// See also [tabContainerId].
|
||||
TabContainerIdProvider call(
|
||||
String tabId,
|
||||
) {
|
||||
return TabContainerIdProvider(
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TabContainerIdProvider getProviderOverride(
|
||||
covariant TabContainerIdProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.tabId,
|
||||
);
|
||||
}
|
||||
|
||||
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'tabContainerIdProvider';
|
||||
}
|
||||
|
||||
/// See also [tabContainerId].
|
||||
class TabContainerIdProvider extends AutoDisposeStreamProvider<String?> {
|
||||
/// See also [tabContainerId].
|
||||
TabContainerIdProvider(
|
||||
String tabId,
|
||||
) : this._internal(
|
||||
(ref) => tabContainerId(
|
||||
ref as TabContainerIdRef,
|
||||
tabId,
|
||||
),
|
||||
from: tabContainerIdProvider,
|
||||
name: r'tabContainerIdProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$tabContainerIdHash,
|
||||
dependencies: TabContainerIdFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TabContainerIdFamily._allTransitiveDependencies,
|
||||
tabId: tabId,
|
||||
);
|
||||
|
||||
TabContainerIdProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.tabId,
|
||||
}) : super.internal();
|
||||
|
||||
final String tabId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<String?> Function(TabContainerIdRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TabContainerIdProvider._internal(
|
||||
(ref) => create(ref as TabContainerIdRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
tabId: tabId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<String?> createElement() {
|
||||
return _TabContainerIdProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabContainerIdProvider && other.tabId == tabId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, tabId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin TabContainerIdRef on AutoDisposeStreamProviderRef<String?> {
|
||||
/// The parameter `tabId` of this provider.
|
||||
String get tabId;
|
||||
}
|
||||
|
||||
class _TabContainerIdProviderElement
|
||||
extends AutoDisposeStreamProviderElement<String?> with TabContainerIdRef {
|
||||
_TabContainerIdProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get tabId => (origin as TabContainerIdProvider).tabId;
|
||||
}
|
||||
|
||||
String _$containerTabIdsHash() => r'7545a6c500b1832bf81c0838e257dfe5e051463d';
|
||||
|
||||
/// See also [containerTabIds].
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/user/domain/services/local_authentication.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -7,15 +10,47 @@ part 'selected_container.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SelectedContainer extends _$SelectedContainer {
|
||||
void setContainerId(String id) {
|
||||
state = id;
|
||||
Future<ContainerData?> fetchData() async {
|
||||
if (state != null) {
|
||||
return ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.getContainerData(state!);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void toggleContainer(String id) {
|
||||
Future<void> setContainerId(String id) async {
|
||||
final container = await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.getContainerData(id);
|
||||
|
||||
if (container != null) {
|
||||
if (container.metadata.authSettings.authenticationRequired) {
|
||||
final authResult = await ref
|
||||
.read(localAuthenticationServiceProvider.notifier)
|
||||
.authenticate(
|
||||
authKey: 'container_access::${container.id}',
|
||||
localizedReason:
|
||||
'Require authentication for container ${container.name ?? 'New Container'}',
|
||||
settings: container.metadata.authSettings,
|
||||
useAuthCache: true,
|
||||
);
|
||||
|
||||
if (authResult) {
|
||||
state = id;
|
||||
}
|
||||
} else {
|
||||
state = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleContainer(String id) async {
|
||||
if (state == id) {
|
||||
clearContainer();
|
||||
} else {
|
||||
setContainerId(id);
|
||||
await setContainerId(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +60,17 @@ class SelectedContainer extends _$SelectedContainer {
|
||||
|
||||
@override
|
||||
String? build() {
|
||||
ref.listen(
|
||||
containersWithCountProvider,
|
||||
(previous, next) {
|
||||
if (state != null && next.valueOrNull != null) {
|
||||
if (!next.value!.any((container) => container.id == state)) {
|
||||
clearContainer();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ final selectedContainerDataProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
|
||||
String _$selectedContainerHash() => r'e38e86db5bd0584af9561156c26ceaea3d23aebf';
|
||||
String _$selectedContainerHash() => r'753f30646c4992981ec538dcfd33fa23aa1f673d';
|
||||
|
||||
/// See also [SelectedContainer].
|
||||
@ProviderFor(SelectedContainer)
|
||||
|
||||
@@ -1,54 +1,72 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'container.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ContainerRepository extends _$ContainerRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
Future<void> addContainer(ContainerData container) {
|
||||
return ref.read(tabDatabaseProvider).containerDao.addContainer(container);
|
||||
}
|
||||
|
||||
Future<void> addContainer({required String? name, required Color color}) {
|
||||
return _db.containerDao.addContainer(name: name, color: color);
|
||||
Future<void> replaceContainer(ContainerData container) {
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.containerDao
|
||||
.replaceContainer(container);
|
||||
}
|
||||
|
||||
Future<void> replaceContainer({
|
||||
required String id,
|
||||
required String? name,
|
||||
required Color color,
|
||||
}) {
|
||||
return _db.containerDao.replaceContainer(id, name: name, color: color);
|
||||
Future<ContainerData?> getContainerData(String id) async {
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.containerDao
|
||||
.getContainerData(id)
|
||||
.getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> deleteContainer(String id) {
|
||||
return _db.containerDao.deleteContainer(id);
|
||||
Future<void> deleteContainer(String id) async {
|
||||
await ref.read(tabDataRepositoryProvider.notifier).closeAllTabs(id);
|
||||
|
||||
return ref.read(tabDatabaseProvider).containerDao.deleteContainer(id);
|
||||
}
|
||||
|
||||
Future<Set<Color>> getDistinctColors() {
|
||||
return _db.containerDao
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.containerDao
|
||||
.getDistinctColors()
|
||||
.get()
|
||||
.then((colors) => colors.toSet());
|
||||
}
|
||||
|
||||
Future<String> getLeadingOrderKey(String? containerId) {
|
||||
return _db.containerDao.generateLeadingOrderKey(containerId).getSingle();
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.containerDao
|
||||
.generateLeadingOrderKey(containerId)
|
||||
.getSingle();
|
||||
}
|
||||
|
||||
Future<String> getTrailingOrderKey(String? containerId) {
|
||||
return _db.containerDao.generateTrailingOrderKey(containerId).getSingle();
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.containerDao
|
||||
.generateTrailingOrderKey(containerId)
|
||||
.getSingle();
|
||||
}
|
||||
|
||||
Future<String> getOrderKeyAfterTab(String tabId, String? containerId) {
|
||||
return _db.containerDao
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.containerDao
|
||||
.generateOrderKeyAfterTabId(containerId, tabId)
|
||||
.getSingle();
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'container.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$containerRepositoryHash() =>
|
||||
r'89ad15861fb213d6a3fa51232fd0ff9943f51e92';
|
||||
r'b965883f2279b6f0148592bbac5ff40a60798f51';
|
||||
|
||||
/// See also [ContainerRepository].
|
||||
@ProviderFor(ContainerRepository)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -7,35 +6,40 @@ part 'tab.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabDataRepository extends _$TabDataRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
}
|
||||
void build() {}
|
||||
|
||||
Future<void> assignContainer(String tabId, String? containerId) {
|
||||
return _db.tabDao.assignContainer(
|
||||
tabId,
|
||||
containerId: containerId,
|
||||
);
|
||||
return ref.read(tabDatabaseProvider).tabDao.assignContainer(
|
||||
tabId,
|
||||
containerId: containerId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> assignOrderKey(String tabId, String orderKey) {
|
||||
return _db.tabDao.assignOrderKey(
|
||||
tabId,
|
||||
orderKey: orderKey,
|
||||
);
|
||||
return ref.read(tabDatabaseProvider).tabDao.assignOrderKey(
|
||||
tabId,
|
||||
orderKey: orderKey,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> closeAllTabs(String? containerId) async {
|
||||
final tabIds = await _db.tabDao.containerTabIds(containerId).get();
|
||||
final tabIds = await ref
|
||||
.read(tabDatabaseProvider)
|
||||
.tabDao
|
||||
.containerTabIds(containerId)
|
||||
.get();
|
||||
|
||||
if (tabIds.isNotEmpty) {
|
||||
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> containerTabId(String tabId) {
|
||||
return _db.tabDao.tabContainerId(tabId).getSingle();
|
||||
return ref
|
||||
.read(tabDatabaseProvider)
|
||||
.tabDao
|
||||
.tabContainerId(tabId)
|
||||
.getSingleOrNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabDataRepositoryHash() => r'c067bbbf319cd95ad869655740fdf49d13159b94';
|
||||
String _$tabDataRepositoryHash() => r'30ae87fbcb37facd565cb28de605814cd7db4ce8';
|
||||
|
||||
/// See also [TabDataRepository].
|
||||
@ProviderFor(TabDataRepository)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -9,8 +8,6 @@ part 'tab_search.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class TabSearchRepository extends _$TabSearchRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
Future<void> addQuery(
|
||||
String input, {
|
||||
int snippetLength = 120,
|
||||
@@ -20,7 +17,9 @@ class TabSearchRepository extends _$TabSearchRepository {
|
||||
}) async {
|
||||
if (input.isNotEmpty) {
|
||||
state = await AsyncValue.guard(
|
||||
() => _db.tabDao
|
||||
() => ref
|
||||
.read(tabDatabaseProvider)
|
||||
.tabDao
|
||||
.queryTabs(
|
||||
matchPrefix: matchPrefix,
|
||||
matchSuffix: matchSuffix,
|
||||
@@ -37,8 +36,6 @@ class TabSearchRepository extends _$TabSearchRepository {
|
||||
|
||||
@override
|
||||
Future<List<TabQueryResult>?> build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
return Future.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'tab_search.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabSearchRepositoryHash() =>
|
||||
r'9bd42f89bcc3a8d94add093076737dd57383bae2';
|
||||
r'57d130f50aa3feb7a31d27f9a5b54234e0e7064f';
|
||||
|
||||
/// See also [TabSearchRepository].
|
||||
@ProviderFor(TabSearchRepository)
|
||||
|
||||
+16
-15
@@ -6,6 +6,7 @@ import 'package:langchain/langchain.dart' as langchain;
|
||||
import 'package:langchain/langchain.dart';
|
||||
import 'package:langchain_openai/langchain_openai.dart';
|
||||
import 'package:lensai/core/models.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
|
||||
@@ -41,21 +42,21 @@ class DocumentRepository extends _$DocumentRepository {
|
||||
chunkOverlap: chunkOverlap,
|
||||
);
|
||||
|
||||
final splittedWithSource = (splitted != null)
|
||||
? (
|
||||
mainDocumentId: splitted.mainDocumentId,
|
||||
parts: splitted.parts
|
||||
.map(
|
||||
(part) => part.copyWith(
|
||||
metadata: {
|
||||
...part.metadata,
|
||||
'source': part.id,
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
)
|
||||
: null;
|
||||
final splittedWithSource = splitted.mapNotNull(
|
||||
(splitted) => (
|
||||
mainDocumentId: splitted.mainDocumentId,
|
||||
parts: splitted.parts
|
||||
.map(
|
||||
(part) => part.copyWith(
|
||||
metadata: {
|
||||
...part.metadata,
|
||||
'source': part.id,
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
),
|
||||
);
|
||||
|
||||
await _vectorDao.insertDocuments(
|
||||
splittedWithSource?.parts ?? [doc],
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ part of 'document.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$documentRepositoryHash() =>
|
||||
r'0c93116e0f9887d452b7c493dd4da45e5bf0c2d4';
|
||||
r'60e5a0c889d9659c8e0e254c59225c2041a0cb56';
|
||||
|
||||
/// See also [DocumentRepository].
|
||||
@ProviderFor(DocumentRepository)
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/uuid.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart';
|
||||
import 'package:lensai/features/user/domain/services/local_authentication.dart';
|
||||
|
||||
enum _DialogMode { create, edit }
|
||||
|
||||
const _timeoutOptions = <DropdownMenuItem<Duration?>>[
|
||||
DropdownMenuItem(
|
||||
child: Text('Immediately'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: Duration(minutes: 1),
|
||||
child: Text('1 minute'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: Duration(minutes: 5),
|
||||
child: Text('5 minutes'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: Duration(minutes: 15),
|
||||
child: Text('15 minutes'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: Duration(hours: 1),
|
||||
child: Text('1 hour'),
|
||||
),
|
||||
];
|
||||
|
||||
class ContainerEditScreen extends HookConsumerWidget {
|
||||
final _DialogMode _mode;
|
||||
|
||||
final ContainerData initialContainer;
|
||||
|
||||
const ContainerEditScreen._({
|
||||
required _DialogMode mode,
|
||||
required this.initialContainer,
|
||||
}) : _mode = mode;
|
||||
|
||||
factory ContainerEditScreen.create({
|
||||
required ContainerData initialContainer,
|
||||
}) {
|
||||
return ContainerEditScreen._(
|
||||
mode: _DialogMode.create,
|
||||
initialContainer: initialContainer,
|
||||
);
|
||||
}
|
||||
|
||||
factory ContainerEditScreen.edit({required ContainerData initialContainer}) {
|
||||
return ContainerEditScreen._(
|
||||
mode: _DialogMode.edit,
|
||||
initialContainer: initialContainer,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedColor = useState(initialContainer.color);
|
||||
final contextualIdentity =
|
||||
useState(initialContainer.metadata.contextualIdentity);
|
||||
final authSettings = useState(initialContainer.metadata.authSettings);
|
||||
|
||||
final textController =
|
||||
useTextEditingController(text: initialContainer.name);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
switch (_mode) {
|
||||
_DialogMode.create => 'New Container',
|
||||
_DialogMode.edit => 'Edit Container',
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final name = textController.text.trim();
|
||||
final container = initialContainer.copyWith(
|
||||
name: name.isNotEmpty ? name : null,
|
||||
color: selectedColor.value,
|
||||
metadata: initialContainer.metadata.mapNotNull(
|
||||
(metadata) => metadata.copyWith(
|
||||
contextualIdentity: contextualIdentity.value,
|
||||
authSettings: authSettings.value,
|
||||
),
|
||||
) ??
|
||||
ContainerMetadata(
|
||||
iconData: null,
|
||||
contextualIdentity: contextualIdentity.value,
|
||||
authSettings: authSettings.value,
|
||||
),
|
||||
);
|
||||
|
||||
//Check for permissions, when auth is set or getting set
|
||||
if (initialContainer
|
||||
.metadata.authSettings.authenticationRequired ||
|
||||
container.metadata.authSettings.authenticationRequired) {
|
||||
final authResult = await ref
|
||||
.read(localAuthenticationServiceProvider.notifier)
|
||||
.authenticate(
|
||||
authKey: 'container_access::${container.id}',
|
||||
localizedReason:
|
||||
'Require authentication for container ${container.name ?? 'New Container'}',
|
||||
);
|
||||
|
||||
if (!authResult) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (_mode) {
|
||||
case _DialogMode.create:
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.addContainer(container);
|
||||
case _DialogMode.edit:
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.replaceContainer(container);
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.check),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: 24,
|
||||
width: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: selectedColor.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: const Text('Name'),
|
||||
),
|
||||
controller: textController,
|
||||
),
|
||||
TextButton.icon(
|
||||
label: const Text('Select Color'),
|
||||
icon: const Icon(Icons.colorize),
|
||||
onPressed: () async {
|
||||
final color = await showDialog<Color?>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
ColorPickerDialog(selectedColor.value),
|
||||
);
|
||||
|
||||
if (color != null) {
|
||||
selectedColor.value = color;
|
||||
}
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
value: contextualIdentity.value != null,
|
||||
title: const Text('Cookie Isolation'),
|
||||
secondary: const Icon(MdiIcons.cookieLock),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onChanged: (_mode == _DialogMode.create)
|
||||
? (value) {
|
||||
contextualIdentity.value = value
|
||||
? initialContainer
|
||||
.metadata.contextualIdentity ??
|
||||
uuid.v4()
|
||||
: null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
value: authSettings.value.authenticationRequired,
|
||||
title: const Text('Require Authentication'),
|
||||
secondary: const Icon(MdiIcons.fingerprint),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onChanged: (value) {
|
||||
authSettings.value = authSettings.value.copyWith
|
||||
.authenticationRequired(value);
|
||||
},
|
||||
),
|
||||
if (authSettings.value.authenticationRequired)
|
||||
CheckboxListTile.adaptive(
|
||||
value: authSettings.value.lockOnAppBackground,
|
||||
title: const Text(
|
||||
'Auto-lock on background',
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
authSettings.value = authSettings.value.copyWith
|
||||
.lockOnAppBackground(value!);
|
||||
},
|
||||
),
|
||||
if (authSettings.value.authenticationRequired)
|
||||
CheckboxListTile.adaptive(
|
||||
value: authSettings.value.lockTimeout != null,
|
||||
title: const Text(
|
||||
'Timeout',
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
final newValue =
|
||||
value! ? _timeoutOptions[1].value : null;
|
||||
|
||||
authSettings.value =
|
||||
authSettings.value.copyWith.lockTimeout(newValue);
|
||||
},
|
||||
secondary: DropdownButton(
|
||||
value: authSettings.value.lockTimeout,
|
||||
items: _timeoutOptions,
|
||||
onChanged: (value) {
|
||||
authSettings.value =
|
||||
authSettings.value.copyWith.lockTimeout(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_mode == _DialogMode.edit)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
label: const Text('Delete'),
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
final result = await showDialog<bool?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Delete Container'),
|
||||
content: const Text(
|
||||
'Are you sure you want to delete this container and close all attached tabs?',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.deleteContainer(initialContainer.id);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+39
-139
@@ -1,119 +1,17 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.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/core/uuid.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/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_list_tile.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class _ContainerTile extends HookWidget {
|
||||
final ContainerData container;
|
||||
final bool isSelected;
|
||||
|
||||
final void Function(ContainerResult edited) onEdit;
|
||||
final void Function() onDelete;
|
||||
final void Function() onTap;
|
||||
|
||||
const _ContainerTile(
|
||||
this.container, {
|
||||
required this.isSelected,
|
||||
required this.onEdit,
|
||||
required this.onDelete,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final menuController = useMemoized(() => MenuController());
|
||||
|
||||
return ListTile(
|
||||
selected: isSelected,
|
||||
leading: CircleAvatar(backgroundColor: container.color),
|
||||
title: Text(container.name ?? 'New Container'),
|
||||
trailing: MenuAnchor(
|
||||
controller: menuController,
|
||||
builder: (context, controller, child) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 8.0),
|
||||
child: Icon(Icons.more_vert),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final result = await showDialog<ContainerResult?>(
|
||||
context: context,
|
||||
builder: (context) => ContainerDialog.edit(
|
||||
name: container.name,
|
||||
initialColor: container.color,
|
||||
),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
onEdit(result);
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.edit),
|
||||
child: const Text('Edit'),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final result = await showDialog<bool?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Delete Container'),
|
||||
content: const Text(
|
||||
'Are you sure you want to delete this container and close all attached tabs?',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
onDelete();
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.delete),
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerListScreen extends HookConsumerWidget {
|
||||
const ContainerListScreen();
|
||||
|
||||
@@ -139,29 +37,36 @@ class ContainerListScreen extends HookConsumerWidget {
|
||||
itemCount: containers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final container = containers[index];
|
||||
return _ContainerTile(
|
||||
container,
|
||||
return Slidable(
|
||||
key: ValueKey(container.id),
|
||||
isSelected: container.id == selectedContainer,
|
||||
onEdit: (edited) async {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.replaceContainer(
|
||||
id: container.id,
|
||||
name: edited.name,
|
||||
color: edited.color,
|
||||
);
|
||||
},
|
||||
onDelete: () async {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.deleteContainer(container.id);
|
||||
},
|
||||
onTap: () {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.toggleContainer(container.id);
|
||||
},
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) async {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.deleteContainer(container.id);
|
||||
},
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.errorContainer,
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onErrorContainer,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ContainerListTile(
|
||||
container,
|
||||
isSelected: container.id == selectedContainer,
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.toggleContainer(container.id);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -170,12 +75,9 @@ class ContainerListScreen extends HookConsumerWidget {
|
||||
error: (error, stackTrace) => SizedBox.shrink(),
|
||||
loading: () => ListView.builder(
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) => _ContainerTile(
|
||||
itemBuilder: (context, index) => ContainerListTile(
|
||||
ContainerData(id: 'null', color: Colors.transparent),
|
||||
isSelected: false,
|
||||
onEdit: (_) {},
|
||||
onDelete: () {},
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -188,17 +90,15 @@ class ContainerListScreen extends HookConsumerWidget {
|
||||
await ref.read(unusedRandomContainerColorProvider.future);
|
||||
|
||||
if (context.mounted) {
|
||||
final result = await showDialog<ContainerResult?>(
|
||||
context: context,
|
||||
builder: (context) => ContainerDialog.create(
|
||||
initialColor: initialColor,
|
||||
),
|
||||
final result = await context.push<ContainerData?>(
|
||||
ContainerCreateRoute().location,
|
||||
extra: ContainerData(id: uuid.v7(), color: initialColor),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.addContainer(name: result.name, color: result.color);
|
||||
.addContainer(result);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
|
||||
|
||||
class ColorPickerDialog extends HookWidget {
|
||||
final Color initialColor;
|
||||
|
||||
const ColorPickerDialog(this.initialColor);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedColor = useState<Color>(initialColor);
|
||||
|
||||
return AlertDialog(
|
||||
titlePadding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 16.0),
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 20.0,
|
||||
right: 20.0,
|
||||
bottom: 24.0,
|
||||
),
|
||||
insetPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
title: const Text('Select Color'),
|
||||
content: MaterialPicker(
|
||||
pickerColor: selectedColor.value,
|
||||
onColorChanged: (value) {
|
||||
selectedColor.value = value;
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop<Color?>(context);
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop<Color?>(context, selectedColor.value);
|
||||
},
|
||||
child: const Text('Select'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,11 @@ 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/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.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/container.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';
|
||||
@@ -90,12 +93,37 @@ class ContainerChips extends HookConsumerWidget {
|
||||
overlayController.hide();
|
||||
},
|
||||
onAcceptWithDetails: (details) async {
|
||||
await ref
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(
|
||||
details.data.tabId,
|
||||
container.id,
|
||||
);
|
||||
.containerTabId(details.data.tabId);
|
||||
|
||||
final containerData =
|
||||
await containerId.mapNotNull(
|
||||
(containerId) => ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.getContainerData(containerId),
|
||||
);
|
||||
|
||||
if (container.metadata.contextualIdentity ==
|
||||
containerData?.metadata.contextualIdentity) {
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(
|
||||
details.data.tabId,
|
||||
container.id,
|
||||
);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.duplicateTab(
|
||||
selectTabId: details.data.tabId,
|
||||
containerId: container.id,
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(details.data.tabId);
|
||||
}
|
||||
|
||||
dragTargetTabId.value = null;
|
||||
overlayController.hide();
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
|
||||
|
||||
typedef ContainerResult = ({String? name, Color color});
|
||||
|
||||
enum _DialogMode { create, edit }
|
||||
|
||||
class ContainerDialog extends HookWidget {
|
||||
final _DialogMode _mode;
|
||||
|
||||
final String? initialName;
|
||||
final Color initialColor;
|
||||
|
||||
const ContainerDialog._({
|
||||
required _DialogMode mode,
|
||||
required this.initialColor,
|
||||
this.initialName,
|
||||
}) : _mode = mode;
|
||||
|
||||
factory ContainerDialog.create({required Color initialColor}) {
|
||||
return ContainerDialog._(
|
||||
mode: _DialogMode.create,
|
||||
initialColor: initialColor,
|
||||
);
|
||||
}
|
||||
|
||||
factory ContainerDialog.edit({
|
||||
required String? name,
|
||||
required Color initialColor,
|
||||
}) {
|
||||
return ContainerDialog._(
|
||||
mode: _DialogMode.edit,
|
||||
initialColor: initialColor,
|
||||
initialName: name,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedColor = useState<Color>(initialColor);
|
||||
final textController = useTextEditingController(text: initialName);
|
||||
|
||||
return SimpleDialog(
|
||||
titlePadding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 16.0),
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 20.0,
|
||||
right: 20.0,
|
||||
bottom: 24.0,
|
||||
),
|
||||
insetPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
title: Text(
|
||||
switch (_mode) {
|
||||
_DialogMode.create => 'New Container',
|
||||
_DialogMode.edit => 'Edit Container',
|
||||
},
|
||||
),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: 24,
|
||||
width: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: selectedColor.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: const Text('Name'),
|
||||
),
|
||||
controller: textController,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
MaterialPicker(
|
||||
pickerColor: selectedColor.value,
|
||||
onColorChanged: (value) {
|
||||
selectedColor.value = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
OverflowBar(
|
||||
alignment: MainAxisAlignment.end,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop<ContainerResult?>(context);
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final name = textController.text.trim();
|
||||
Navigator.pop<ContainerResult?>(
|
||||
context,
|
||||
(
|
||||
name: name.isNotEmpty ? name : null,
|
||||
color: selectedColor.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
switch (_mode) {
|
||||
_DialogMode.create => 'Add',
|
||||
_DialogMode.edit => 'Edit',
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
class ContainerListTile extends HookWidget {
|
||||
final ContainerData container;
|
||||
final bool isSelected;
|
||||
|
||||
final void Function()? onTap;
|
||||
|
||||
const ContainerListTile(
|
||||
this.container, {
|
||||
required this.isSelected,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTileTheme(
|
||||
selectedColor: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
selectedTileColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: ListTile(
|
||||
selected: isSelected,
|
||||
leading: CircleAvatar(backgroundColor: container.color),
|
||||
title: Text(container.name ?? 'New Container'),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
await context.push(
|
||||
ContainerEditRoute().location,
|
||||
extra: container,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:lensai/features/geckoview/features/preferences/data/models/preference_setting.dart';
|
||||
|
||||
enum PreferencePartition {
|
||||
user('user'),
|
||||
system('system');
|
||||
|
||||
final String key;
|
||||
|
||||
const PreferencePartition(this.key);
|
||||
}
|
||||
|
||||
Map<String, PreferenceSettingGroup> deserializePreferenceSettingGroups(
|
||||
PreferencePartition partition,
|
||||
Map<String, dynamic> content,
|
||||
) {
|
||||
final parititonedContent = content[partition.key] as Map<String, dynamic>;
|
||||
|
||||
return parititonedContent.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>),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class KagiChatService extends _$KagiChatService {
|
||||
throw Exception('Not implemented !');
|
||||
|
||||
// final kagiSession =
|
||||
// ref.read(settingsRepositoryProvider).valueOrNull?.kagiSession;
|
||||
// ref.read(generalSettingsRepositoryProvider).valueOrNull?.kagiSession;
|
||||
|
||||
// return Result.fromAsync(
|
||||
// () async {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// @override
|
||||
// Widget build(BuildContext context, WidgetRef ref) {
|
||||
// final incognitoEnabled = ref.watch(
|
||||
// settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
// generalSettingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
// );
|
||||
|
||||
// final researchVariant = ref.watch(activeResearchVariantProvider);
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:lensai/features/geckoview/features/browser/presentation/widgets/
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
||||
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
@@ -21,11 +21,7 @@ class _InputField extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => value.incognitoMode,
|
||||
),
|
||||
);
|
||||
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
|
||||
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
@@ -223,9 +224,9 @@ class SearchScreen extends HookConsumerWidget {
|
||||
result.extractedContent ?? result.fullContent;
|
||||
|
||||
return ListTile(
|
||||
title: (result.title != null)
|
||||
? MarkdownBody(data: result.title!)
|
||||
: null,
|
||||
title: result.title.mapNotNull(
|
||||
(title) => MarkdownBody(data: title),
|
||||
),
|
||||
subtitle: (!headHasMatch && bodyResult != null)
|
||||
? MarkdownBody(data: bodyResult)
|
||||
: null,
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
||||
|
||||
class SearchField extends HookConsumerWidget {
|
||||
@@ -25,11 +25,7 @@ class SearchField extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => value.incognitoMode,
|
||||
),
|
||||
);
|
||||
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
|
||||
|
||||
final hasText = useListenableSelector(
|
||||
textEditingController,
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'save_settings.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SaveSettingsController extends _$SaveSettingsController {
|
||||
class SaveGeneralSettingsController extends _$SaveGeneralSettingsController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> save(UpdateSettingsFunc updateSettings) async {
|
||||
Future<void> save(UpdateGeneralSettingsFunc updateSettings) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(
|
||||
() => ref
|
||||
.read(settingsRepositoryProvider.notifier)
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(updateSettings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SaveEngineSettingsController extends _$SaveEngineSettingsController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<void> save(UpdateEngineSettingsFunc updateSettings) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(
|
||||
() => ref
|
||||
.read(engineSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(updateSettings),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,22 +6,39 @@ part of 'save_settings.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$saveSettingsControllerHash() =>
|
||||
r'7ffcef95b48dce1f80b56fb3580282ff3df240b4';
|
||||
String _$saveGeneralSettingsControllerHash() =>
|
||||
r'ad8b60839cbcb05a964f57e32fc5d9c1aba30377';
|
||||
|
||||
/// See also [SaveSettingsController].
|
||||
@ProviderFor(SaveSettingsController)
|
||||
final saveSettingsControllerProvider =
|
||||
AsyncNotifierProvider<SaveSettingsController, void>.internal(
|
||||
SaveSettingsController.new,
|
||||
name: r'saveSettingsControllerProvider',
|
||||
/// See also [SaveGeneralSettingsController].
|
||||
@ProviderFor(SaveGeneralSettingsController)
|
||||
final saveGeneralSettingsControllerProvider =
|
||||
AsyncNotifierProvider<SaveGeneralSettingsController, void>.internal(
|
||||
SaveGeneralSettingsController.new,
|
||||
name: r'saveGeneralSettingsControllerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$saveSettingsControllerHash,
|
||||
: _$saveGeneralSettingsControllerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SaveSettingsController = AsyncNotifier<void>;
|
||||
typedef _$SaveGeneralSettingsController = AsyncNotifier<void>;
|
||||
String _$saveEngineSettingsControllerHash() =>
|
||||
r'4ca0ee76a41a72a8e6eacdb0e39045c667390c00';
|
||||
|
||||
/// See also [SaveEngineSettingsController].
|
||||
@ProviderFor(SaveEngineSettingsController)
|
||||
final saveEngineSettingsControllerProvider =
|
||||
AsyncNotifierProvider<SaveEngineSettingsController, void>.internal(
|
||||
SaveEngineSettingsController.new,
|
||||
name: r'saveEngineSettingsControllerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$saveEngineSettingsControllerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SaveEngineSettingsController = AsyncNotifier<void>;
|
||||
// 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
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/widgets/bang_group_list_tile.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/sections.dart';
|
||||
|
||||
class BangSettingsScreen extends HookConsumerWidget {
|
||||
const BangSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Bang Settings')),
|
||||
body: FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
CustomListTile(
|
||||
title: 'Bang Frequencies',
|
||||
subtitle: 'Tracked usage for Bang recommendations',
|
||||
suffix: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.resetFrequencies();
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
label: const Text('Clear'),
|
||||
),
|
||||
),
|
||||
const SettingSubSection(name: 'Repositories'),
|
||||
const BangGroupListTile(
|
||||
group: BangGroup.general,
|
||||
title: 'General Bangs',
|
||||
subtitle: 'Automatically syncs every 7 days',
|
||||
),
|
||||
const BangGroupListTile(
|
||||
group: BangGroup.assistant,
|
||||
title: 'Assistant Bangs',
|
||||
subtitle: 'Automatically syncs every 7 days',
|
||||
),
|
||||
const BangGroupListTile(
|
||||
group: BangGroup.kagi,
|
||||
title: 'Kagi Bangs',
|
||||
subtitle: 'Automatically syncs every 7 days',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart';
|
||||
import 'package:lensai/features/user/data/models/general_settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
|
||||
class GeneralSettingsScreen extends HookConsumerWidget {
|
||||
const GeneralSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final generalSettings = ref.watch(generalSettingsRepositoryProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('General Settings')),
|
||||
body: FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Theme'),
|
||||
leading: Icon(Icons.palette),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
Center(
|
||||
child: SegmentedButton<ThemeMode>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: ThemeMode.system,
|
||||
icon: Icon(Icons.brightness_auto),
|
||||
label: Text('System'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ThemeMode.light,
|
||||
icon: Icon(Icons.light_mode),
|
||||
label: Text('Light'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ThemeMode.dark,
|
||||
icon: Icon(Icons.dark_mode),
|
||||
label: Text('Dark'),
|
||||
),
|
||||
],
|
||||
selected: {generalSettings.themeMode},
|
||||
onSelectionChanged: (value) async {
|
||||
await ref
|
||||
.read(saveGeneralSettingsControllerProvider
|
||||
.notifier)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.themeMode(value.first),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable Reader Mode'),
|
||||
subtitle: const Text(
|
||||
'Optional browser app bar tool that extracts and simplifies web pages for improved readability by removing ads, sidebars, and other non-essential elements.',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.bookOpen),
|
||||
value: generalSettings.enableReadability,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.enableReadability(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final size = ref.watch(
|
||||
iconCacheSizeMegabytesProvider.select(
|
||||
(value) => value.valueOrNull,
|
||||
),
|
||||
);
|
||||
|
||||
return CustomListTile(
|
||||
title: 'Icon Cache',
|
||||
subtitle: 'Stored favicons',
|
||||
prefix: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Icon(
|
||||
Icons.image,
|
||||
size: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: DefaultTextStyle(
|
||||
style: GoogleFonts.robotoMono(
|
||||
textStyle: DefaultTextStyle.of(context).style,
|
||||
),
|
||||
child: Table(
|
||||
columnWidths: const {0: FixedColumnWidth(100)},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
const Text('Size'),
|
||||
Text('${size?.toStringAsFixed(2) ?? 0} MB'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
suffix: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(cacheRepositoryProvider.notifier)
|
||||
.clearCache();
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
label: const Text('Clear'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,15 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.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';
|
||||
import 'package:lensai/features/settings/presentation/widgets/bang_group_list_tile.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart';
|
||||
import 'package:lensai/features/user/data/models/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
|
||||
class SettingsScreen extends HookConsumerWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
Widget _buildSection(ThemeData theme, String name) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(name, style: theme.textTheme.titleLarge),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubSection(ThemeData theme, String name) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
child: Text(
|
||||
name,
|
||||
style: theme.textTheme.titleLarge?.copyWith(fontSize: 18),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final settings = ref.watch(settingsRepositoryProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
body: FadingScroll(
|
||||
@@ -50,189 +17,61 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
builder: (context, controller) {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
children: [
|
||||
_buildSection(theme, 'General'),
|
||||
Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Browser Hardening'),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
await context.push(BrowserHardeningRoute().location);
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
title: const Text('General'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
leading: const Icon(Icons.settings),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await context.push(GeneralSettingsRoute().location);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Theme',
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
Center(
|
||||
child: SegmentedButton<ThemeMode>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: ThemeMode.system,
|
||||
icon: Icon(Icons.brightness_auto),
|
||||
label: Text('System'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ThemeMode.light,
|
||||
icon: Icon(Icons.light_mode),
|
||||
label: Text('Light'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ThemeMode.dark,
|
||||
icon: Icon(Icons.dark_mode),
|
||||
label: Text('Dark'),
|
||||
),
|
||||
],
|
||||
selected: {settings.themeMode},
|
||||
onSelectionChanged: (value) async {
|
||||
await ref
|
||||
.read(saveSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.themeMode(value.first),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Incognito Mode'),
|
||||
subtitle: const Text(
|
||||
'Deletes all browsing data upon app restart for enhanced privacy.',
|
||||
),
|
||||
value: settings.incognitoMode,
|
||||
onChanged: (value) async {
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.incognitoMode(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable JavaScript'),
|
||||
subtitle: const Text(
|
||||
'While turning off JavaScript boosts security, privacy, and speed, it may cause some sites to not work as intended.',
|
||||
),
|
||||
value: settings.enableJavascript,
|
||||
onChanged: (value) async {
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.enableJavascript(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Block HTTP Protocol'),
|
||||
subtitle: const Text(
|
||||
'Prevents the loading of unsecure HTTP content. When enabled, only secure HTTPS content is allowed.',
|
||||
),
|
||||
value: settings.blockHttpProtocol,
|
||||
onChanged: (value) async {
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.blockHttpProtocol(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable Reader Mode'),
|
||||
subtitle: const Text(
|
||||
'Optional browser app bar tool that extracts and simplifies web pages for improved readability by removing ads, sidebars, and other non-essential elements.',
|
||||
),
|
||||
value: settings.enableReadability,
|
||||
onChanged: (value) async {
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.enableReadability(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
height: 8,
|
||||
),
|
||||
_buildSection(theme, 'Bangs'),
|
||||
CustomListTile(
|
||||
title: 'Bang Frequencies',
|
||||
subtitle: 'Tracked usage for Bang recommendations',
|
||||
suffix: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.resetFrequencies();
|
||||
Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Web Engine'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
leading: const Icon(MdiIcons.web),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await context.push(WebEngineSettingsRoute().location);
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
label: const Text('Clear'),
|
||||
),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final size = ref.watch(
|
||||
iconCacheSizeMegabytesProvider.select(
|
||||
(value) => value.valueOrNull,
|
||||
),
|
||||
);
|
||||
|
||||
return CustomListTile(
|
||||
title: 'Icon Cache',
|
||||
subtitle: 'Stored favicons for Bangs',
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: DefaultTextStyle(
|
||||
style: GoogleFonts.robotoMono(
|
||||
textStyle: DefaultTextStyle.of(context).style,
|
||||
),
|
||||
child: Table(
|
||||
columnWidths: const {0: FixedColumnWidth(100)},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
const Text('Size'),
|
||||
Text('${size?.toStringAsFixed(2) ?? 0} MB'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
suffix: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(cacheRepositoryProvider.notifier)
|
||||
.clearCache();
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
label: const Text('Clear'),
|
||||
),
|
||||
);
|
||||
},
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
_buildSubSection(theme, 'Repositories'),
|
||||
const BangGroupListTile(
|
||||
group: BangGroup.general,
|
||||
title: 'General Bangs',
|
||||
subtitle: 'Automatically syncs every 7 days',
|
||||
),
|
||||
const BangGroupListTile(
|
||||
group: BangGroup.assistant,
|
||||
title: 'Assistant Bangs',
|
||||
subtitle: 'Automatically syncs every 7 days',
|
||||
),
|
||||
const BangGroupListTile(
|
||||
group: BangGroup.kagi,
|
||||
title: 'Kagi Bangs',
|
||||
subtitle: 'Automatically syncs every 7 days',
|
||||
Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Bangs'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
leading: const Icon(MdiIcons.exclamationThick),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await context.push(BangSettingsRoute().location);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
+20
-12
@@ -4,17 +4,20 @@ 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/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/features/preferences/data/repositories/preference_settings.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/utils/setting_groups_serializer.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();
|
||||
class WebEngineHardeningScreen extends HookConsumerWidget {
|
||||
const WebEngineHardeningScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final preferenceGroups =
|
||||
ref.watch(preferenceSettingsGeneralRepositoryProvider);
|
||||
final preferenceGroups = ref.watch(
|
||||
unifiedPreferenceSettingsRepositoryProvider(PreferencePartition.user),
|
||||
);
|
||||
|
||||
final allGroupsActive = useMemoized(
|
||||
() =>
|
||||
@@ -28,7 +31,7 @@ class BrowserHardeningScreen extends HookConsumerWidget {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Browser Hardening')),
|
||||
appBar: AppBar(title: const Text('Web Engine Hardening')),
|
||||
body: preferenceGroups.when(
|
||||
data: (data) {
|
||||
return Column(
|
||||
@@ -49,7 +52,9 @@ class BrowserHardeningScreen extends HookConsumerWidget {
|
||||
),
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
preferenceSettingsGeneralRepositoryProvider.notifier,
|
||||
unifiedPreferenceSettingsRepositoryProvider(
|
||||
PreferencePartition.user,
|
||||
).notifier,
|
||||
);
|
||||
|
||||
if (value) {
|
||||
@@ -71,9 +76,9 @@ class BrowserHardeningScreen extends HookConsumerWidget {
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
title: Text(group.key),
|
||||
subtitle: (group.value.description != null)
|
||||
? Text(group.value.description!)
|
||||
: null,
|
||||
subtitle: group.value.description.mapNotNull(
|
||||
(description) => Text(description),
|
||||
),
|
||||
leading: HardeningGroupIcon(
|
||||
isActive: group.value.isActive,
|
||||
isPartlyActive: group.value.isPartlyActive,
|
||||
@@ -81,7 +86,7 @@ class BrowserHardeningScreen extends HookConsumerWidget {
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await context.push(
|
||||
BrowserHardeningGroupRoute(
|
||||
WebEngineHardeningGroupRoute(
|
||||
group: group.key,
|
||||
).location,
|
||||
);
|
||||
@@ -100,8 +105,11 @@ class BrowserHardeningScreen extends HookConsumerWidget {
|
||||
error: (error, stackTrace) => FailureWidget(
|
||||
title: 'Could not load preference settings',
|
||||
exception: error,
|
||||
onRetry: () =>
|
||||
ref.refresh(preferenceSettingsGeneralRepositoryProvider),
|
||||
onRetry: () => ref.refresh(
|
||||
unifiedPreferenceSettingsRepositoryProvider(
|
||||
PreferencePartition.user,
|
||||
),
|
||||
),
|
||||
),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
+40
-27
@@ -1,18 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/geckoview/features/preferences/data/repositories/preference_settings.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/utils/setting_groups_serializer.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/hardening_group_icon.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
class BrowserHardeningGroupScreen extends HookConsumerWidget {
|
||||
class WebEngineHardeningGroupScreen extends HookConsumerWidget {
|
||||
final String groupName;
|
||||
|
||||
const BrowserHardeningGroupScreen({super.key, required this.groupName});
|
||||
const WebEngineHardeningGroupScreen({super.key, required this.groupName});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settings =
|
||||
ref.watch(preferenceSettingsGroupRepositoryProvider(groupName));
|
||||
final settings = ref.watch(
|
||||
preferenceSettingsGroupRepositoryProvider(
|
||||
PreferencePartition.user,
|
||||
groupName,
|
||||
),
|
||||
);
|
||||
|
||||
final theme = Theme.of(context);
|
||||
|
||||
@@ -36,18 +42,20 @@ class BrowserHardeningGroupScreen extends HookConsumerWidget {
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
subtitle: (group.description != null)
|
||||
? Text(
|
||||
group.description!,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
subtitle: group.description.mapNotNull(
|
||||
(description) => Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
preferenceSettingsGroupRepositoryProvider(groupName)
|
||||
.notifier,
|
||||
preferenceSettingsGroupRepositoryProvider(
|
||||
PreferencePartition.user,
|
||||
groupName,
|
||||
).notifier,
|
||||
);
|
||||
|
||||
if (value) {
|
||||
@@ -71,16 +79,17 @@ class BrowserHardeningGroupScreen extends HookConsumerWidget {
|
||||
Expanded(
|
||||
child: SwitchListTile(
|
||||
value: setting.value.isActive,
|
||||
title: Text(setting.value.title),
|
||||
subtitle: (setting.value.description != null)
|
||||
? Text(setting.value.description!)
|
||||
: null,
|
||||
title: Text(setting.value.title ?? setting.key),
|
||||
subtitle: setting.value.description.mapNotNull(
|
||||
(description) => Text(description),
|
||||
),
|
||||
secondary: HardeningGroupIcon(
|
||||
isActive: setting.value.isActive,
|
||||
),
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
preferenceSettingsGroupRepositoryProvider(
|
||||
PreferencePartition.user,
|
||||
groupName,
|
||||
).notifier,
|
||||
);
|
||||
@@ -96,16 +105,16 @@ class BrowserHardeningGroupScreen extends HookConsumerWidget {
|
||||
else
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
title: Text(setting.value.title),
|
||||
subtitle: (setting.value.description != null)
|
||||
? Text(setting.value.description!)
|
||||
: null,
|
||||
title: Text(setting.value.title ?? setting.key),
|
||||
subtitle: setting.value.description.mapNotNull(
|
||||
(description) => Text(description),
|
||||
),
|
||||
leading: HardeningGroupIcon(
|
||||
isActive: setting.value.isActive,
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.only(right: 18.0),
|
||||
child: const Icon(Icons.check),
|
||||
trailing: const Padding(
|
||||
padding: EdgeInsets.only(right: 18.0),
|
||||
child: Icon(Icons.check),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -121,8 +130,12 @@ class BrowserHardeningGroupScreen extends HookConsumerWidget {
|
||||
error: (error, stackTrace) => FailureWidget(
|
||||
title: 'Could not load preference settings',
|
||||
exception: error,
|
||||
onRetry: () =>
|
||||
ref.refresh(preferenceSettingsGroupRepositoryProvider(groupName)),
|
||||
onRetry: () => ref.refresh(
|
||||
preferenceSettingsGroupRepositoryProvider(
|
||||
PreferencePartition.user,
|
||||
groupName,
|
||||
),
|
||||
),
|
||||
),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
@@ -0,0 +1,315 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
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/routing/routes.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||
import 'package:lensai/features/user/data/models/engine_settings.dart';
|
||||
import 'package:lensai/features/user/data/models/general_settings.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
|
||||
class WebEngineSettingsScreen extends HookConsumerWidget {
|
||||
const WebEngineSettingsScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final generalSettings = ref.watch(generalSettingsRepositoryProvider);
|
||||
final engineSettings = ref.watch(engineSettingsRepositoryProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Web Engine Settings')),
|
||||
body: FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Incognito Mode'),
|
||||
subtitle: const Text(
|
||||
'Deletes all browsing data upon app restart for enhanced privacy.',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.incognito),
|
||||
value: generalSettings.deleteBrowsingDataOnQuit != null,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) => value
|
||||
? currentSettings.copyWith
|
||||
.deleteBrowsingDataOnQuit({})
|
||||
: currentSettings.copyWith
|
||||
.deleteBrowsingDataOnQuit(null),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (generalSettings.deleteBrowsingDataOnQuit != null)
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
for (final type in DeleteBrowsingDataType.values)
|
||||
CheckboxListTile.adaptive(
|
||||
value: generalSettings.deleteBrowsingDataOnQuit!
|
||||
.contains(type),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: Text(type.title),
|
||||
subtitle: type.description
|
||||
.mapNotNull((description) => Text(description)),
|
||||
onChanged: (value) async {
|
||||
final notifier = ref.read(
|
||||
saveGeneralSettingsControllerProvider.notifier,
|
||||
);
|
||||
|
||||
if (value == true) {
|
||||
await notifier.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.deleteBrowsingDataOnQuit({
|
||||
...currentSettings.deleteBrowsingDataOnQuit!,
|
||||
type,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
await notifier.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.deleteBrowsingDataOnQuit(
|
||||
{
|
||||
...currentSettings
|
||||
.deleteBrowsingDataOnQuit!,
|
||||
}..remove(type),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable JavaScript'),
|
||||
subtitle: const Text(
|
||||
'While turning off JavaScript boosts security, privacy, and speed, it may cause some sites to not work as intended.',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.languageJavascript),
|
||||
value: engineSettings.javascriptEnabled,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(saveEngineSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.javascriptEnabled(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable Global Privacy Control (GPC)'),
|
||||
secondary: const Icon(MdiIcons.incognitoCircleOff),
|
||||
value: engineSettings.globalPrivacyControlEnabled,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(saveEngineSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.globalPrivacyControlEnabled(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Block insecure HTTP connections'),
|
||||
leading: Icon(MdiIcons.lockOpen),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
Center(
|
||||
child: SegmentedButton<HttpsOnlyMode>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: HttpsOnlyMode.disabled,
|
||||
label: Text('Disabled'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: HttpsOnlyMode.enabled,
|
||||
label: Text('Enabled'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: HttpsOnlyMode.privateOnly,
|
||||
label: Text('Private mode only'),
|
||||
),
|
||||
],
|
||||
selected: {engineSettings.httpsOnlyMode},
|
||||
onSelectionChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveEngineSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.httpsOnlyMode(value.first),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Enhanced Tracking Protection'),
|
||||
leading: Icon(MdiIcons.incognitoCircleOff),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
RadioListTile<TrackingProtectionPolicy>.adaptive(
|
||||
value: TrackingProtectionPolicy.none,
|
||||
title: const Text('Disabled'),
|
||||
groupValue: engineSettings.trackingProtectionPolicy,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveEngineSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.trackingProtectionPolicy(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
RadioListTile<TrackingProtectionPolicy>.adaptive(
|
||||
value: TrackingProtectionPolicy.recommended,
|
||||
title: const Text('Standard'),
|
||||
subtitle: const Text(
|
||||
'Pages will load normally, but block fewer trackers.',
|
||||
),
|
||||
groupValue: engineSettings.trackingProtectionPolicy,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveEngineSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.trackingProtectionPolicy(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
RadioListTile<TrackingProtectionPolicy>.adaptive(
|
||||
value: TrackingProtectionPolicy.strict,
|
||||
title: const Text('Strict'),
|
||||
subtitle: const Text(
|
||||
'Stronger tracking protection and faster performance, but some sites may not work properly.',
|
||||
),
|
||||
groupValue: engineSettings.trackingProtectionPolicy,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveEngineSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.trackingProtectionPolicy(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
RadioListTile<TrackingProtectionPolicy>.adaptive(
|
||||
value: TrackingProtectionPolicy.custom,
|
||||
title: const Text('Custom'),
|
||||
subtitle: const Text(
|
||||
'Choose which trackers and scripts to block.',
|
||||
),
|
||||
groupValue: engineSettings.trackingProtectionPolicy,
|
||||
onChanged: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Cookie Banner Blocker'),
|
||||
leading: Icon(MdiIcons.cookieSettings),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
CheckboxListTile.adaptive(
|
||||
value: engineSettings.cookieBannerHandlingMode ==
|
||||
CookieBannerHandlingMode.rejectAll,
|
||||
title: const Text('In normal browsing'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveEngineSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.cookieBannerHandlingMode(
|
||||
value!
|
||||
? CookieBannerHandlingMode.rejectAll
|
||||
: CookieBannerHandlingMode.disabled,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
CheckboxListTile.adaptive(
|
||||
value: engineSettings
|
||||
.cookieBannerHandlingModePrivateBrowsing ==
|
||||
CookieBannerHandlingMode.rejectAll,
|
||||
title: const Text('In private browsing'),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveEngineSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.cookieBannerHandlingModePrivateBrowsing(
|
||||
value!
|
||||
? CookieBannerHandlingMode.rejectAll
|
||||
: CookieBannerHandlingMode.disabled,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Web Engine Hardening'),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
leading: const Icon(MdiIcons.shieldLock),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
await context.push(WebEngineHardeningRoute().location);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingSection extends StatelessWidget {
|
||||
final String name;
|
||||
|
||||
const SettingSection({required this.name});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(name, style: Theme.of(context).textTheme.titleLarge),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingSubSection extends StatelessWidget {
|
||||
final String name;
|
||||
|
||||
const SettingSubSection({required this.name});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
child: Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontSize: 18),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/user/data/database/database.dart';
|
||||
|
||||
part 'setting.g.dart';
|
||||
@@ -7,12 +10,16 @@ part 'setting.g.dart';
|
||||
class SettingDao extends DatabaseAccessor<UserDatabase> with _$SettingDaoMixin {
|
||||
SettingDao(super.attachedDatabase);
|
||||
|
||||
Future<int> updateSetting(String key, Object? value) {
|
||||
final driftvalue = (value != null) ? DriftAny(value) : null;
|
||||
Future<int> updateSetting(String key, String? partitionKey, Object? value) {
|
||||
final normalizedValue = (value is Iterable) ? jsonEncode(value) : value;
|
||||
|
||||
final driftvalue = normalizedValue
|
||||
.mapNotNull((normalizedValue) => DriftAny(normalizedValue));
|
||||
|
||||
return db.setting.insertOne(
|
||||
SettingCompanion.insert(
|
||||
key: key,
|
||||
partitionKey: Value(partitionKey),
|
||||
value: Value(driftvalue),
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
@@ -21,7 +28,12 @@ class SettingDao extends DatabaseAccessor<UserDatabase> with _$SettingDaoMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<MapEntry<String, DriftAny?>> allSettings() {
|
||||
return db.setting.select().map((row) => MapEntry(row.key, row.value));
|
||||
Selectable<MapEntry<String, DriftAny?>> allSettingsOfPartitionKey(
|
||||
String? partitionKey,
|
||||
) {
|
||||
final query = db.setting.select()
|
||||
..where((r) => r.partitionKey.equalsNullable(partitionKey));
|
||||
|
||||
return query.map((row) => MapEntry(row.key, row.value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import 'package:lensai/features/user/data/database/daos/setting.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(include: {
|
||||
'database.drift'
|
||||
}, daos: [
|
||||
SettingDao,
|
||||
CacheDao,
|
||||
])
|
||||
@DriftDatabase(
|
||||
include: {
|
||||
'database.drift',
|
||||
},
|
||||
daos: [
|
||||
SettingDao,
|
||||
CacheDao,
|
||||
],
|
||||
)
|
||||
class UserDatabase extends _$UserDatabase {
|
||||
@override
|
||||
final int schemaVersion = 1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
CREATE TABLE setting (
|
||||
"key" TEXT PRIMARY KEY NOT NULL,
|
||||
partition_key TEXT,
|
||||
"value" ANY
|
||||
) STRICT;
|
||||
|
||||
|
||||
@@ -13,13 +13,18 @@ class Setting extends Table with TableInfo<Setting, SettingData> {
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> partitionKey = GeneratedColumn<String>(
|
||||
'partition_key', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
late final GeneratedColumn<DriftAny> value = GeneratedColumn<DriftAny>(
|
||||
'value', aliasedName, true,
|
||||
type: DriftSqlType.any,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [key, value];
|
||||
List<GeneratedColumn> get $columns => [key, partitionKey, value];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
@@ -33,6 +38,8 @@ class Setting extends Table with TableInfo<Setting, SettingData> {
|
||||
return SettingData(
|
||||
key: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}key'])!,
|
||||
partitionKey: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}partition_key']),
|
||||
value: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.any, data['${effectivePrefix}value']),
|
||||
);
|
||||
@@ -51,12 +58,16 @@ class Setting extends Table with TableInfo<Setting, SettingData> {
|
||||
|
||||
class SettingData extends DataClass implements Insertable<SettingData> {
|
||||
final String key;
|
||||
final String? partitionKey;
|
||||
final DriftAny? value;
|
||||
const SettingData({required this.key, this.value});
|
||||
const SettingData({required this.key, this.partitionKey, this.value});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['key'] = Variable<String>(key);
|
||||
if (!nullToAbsent || partitionKey != null) {
|
||||
map['partition_key'] = Variable<String>(partitionKey);
|
||||
}
|
||||
if (!nullToAbsent || value != null) {
|
||||
map['value'] = Variable<DriftAny>(value);
|
||||
}
|
||||
@@ -68,6 +79,7 @@ class SettingData extends DataClass implements Insertable<SettingData> {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return SettingData(
|
||||
key: serializer.fromJson<String>(json['key']),
|
||||
partitionKey: serializer.fromJson<String?>(json['partition_key']),
|
||||
value: serializer.fromJson<DriftAny?>(json['value']),
|
||||
);
|
||||
}
|
||||
@@ -76,19 +88,27 @@ class SettingData extends DataClass implements Insertable<SettingData> {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'key': serializer.toJson<String>(key),
|
||||
'partition_key': serializer.toJson<String?>(partitionKey),
|
||||
'value': serializer.toJson<DriftAny?>(value),
|
||||
};
|
||||
}
|
||||
|
||||
SettingData copyWith(
|
||||
{String? key, Value<DriftAny?> value = const Value.absent()}) =>
|
||||
{String? key,
|
||||
Value<String?> partitionKey = const Value.absent(),
|
||||
Value<DriftAny?> value = const Value.absent()}) =>
|
||||
SettingData(
|
||||
key: key ?? this.key,
|
||||
partitionKey:
|
||||
partitionKey.present ? partitionKey.value : this.partitionKey,
|
||||
value: value.present ? value.value : this.value,
|
||||
);
|
||||
SettingData copyWithCompanion(SettingCompanion data) {
|
||||
return SettingData(
|
||||
key: data.key.present ? data.key.value : this.key,
|
||||
partitionKey: data.partitionKey.present
|
||||
? data.partitionKey.value
|
||||
: this.partitionKey,
|
||||
value: data.value.present ? data.value.value : this.value,
|
||||
);
|
||||
}
|
||||
@@ -97,51 +117,62 @@ class SettingData extends DataClass implements Insertable<SettingData> {
|
||||
String toString() {
|
||||
return (StringBuffer('SettingData(')
|
||||
..write('key: $key, ')
|
||||
..write('partitionKey: $partitionKey, ')
|
||||
..write('value: $value')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(key, value);
|
||||
int get hashCode => Object.hash(key, partitionKey, value);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SettingData &&
|
||||
other.key == this.key &&
|
||||
other.partitionKey == this.partitionKey &&
|
||||
other.value == this.value);
|
||||
}
|
||||
|
||||
class SettingCompanion extends UpdateCompanion<SettingData> {
|
||||
final Value<String> key;
|
||||
final Value<String?> partitionKey;
|
||||
final Value<DriftAny?> value;
|
||||
final Value<int> rowid;
|
||||
const SettingCompanion({
|
||||
this.key = const Value.absent(),
|
||||
this.partitionKey = const Value.absent(),
|
||||
this.value = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
SettingCompanion.insert({
|
||||
required String key,
|
||||
this.partitionKey = const Value.absent(),
|
||||
this.value = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : key = Value(key);
|
||||
static Insertable<SettingData> custom({
|
||||
Expression<String>? key,
|
||||
Expression<String>? partitionKey,
|
||||
Expression<DriftAny>? value,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (key != null) 'key': key,
|
||||
if (partitionKey != null) 'partition_key': partitionKey,
|
||||
if (value != null) 'value': value,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
SettingCompanion copyWith(
|
||||
{Value<String>? key, Value<DriftAny?>? value, Value<int>? rowid}) {
|
||||
{Value<String>? key,
|
||||
Value<String?>? partitionKey,
|
||||
Value<DriftAny?>? value,
|
||||
Value<int>? rowid}) {
|
||||
return SettingCompanion(
|
||||
key: key ?? this.key,
|
||||
partitionKey: partitionKey ?? this.partitionKey,
|
||||
value: value ?? this.value,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
@@ -153,6 +184,9 @@ class SettingCompanion extends UpdateCompanion<SettingData> {
|
||||
if (key.present) {
|
||||
map['key'] = Variable<String>(key.value);
|
||||
}
|
||||
if (partitionKey.present) {
|
||||
map['partition_key'] = Variable<String>(partitionKey.value);
|
||||
}
|
||||
if (value.present) {
|
||||
map['value'] = Variable<DriftAny>(value.value);
|
||||
}
|
||||
@@ -166,6 +200,7 @@ class SettingCompanion extends UpdateCompanion<SettingData> {
|
||||
String toString() {
|
||||
return (StringBuffer('SettingCompanion(')
|
||||
..write('key: $key, ')
|
||||
..write('partitionKey: $partitionKey, ')
|
||||
..write('value: $value, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
@@ -396,11 +431,13 @@ abstract class _$UserDatabase extends GeneratedDatabase {
|
||||
|
||||
typedef $SettingCreateCompanionBuilder = SettingCompanion Function({
|
||||
required String key,
|
||||
Value<String?> partitionKey,
|
||||
Value<DriftAny?> value,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $SettingUpdateCompanionBuilder = SettingCompanion Function({
|
||||
Value<String> key,
|
||||
Value<String?> partitionKey,
|
||||
Value<DriftAny?> value,
|
||||
Value<int> rowid,
|
||||
});
|
||||
@@ -416,6 +453,9 @@ class $SettingFilterComposer extends Composer<_$UserDatabase, Setting> {
|
||||
ColumnFilters<String> get key => $composableBuilder(
|
||||
column: $table.key, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get partitionKey => $composableBuilder(
|
||||
column: $table.partitionKey, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DriftAny> get value => $composableBuilder(
|
||||
column: $table.value, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
@@ -431,6 +471,10 @@ class $SettingOrderingComposer extends Composer<_$UserDatabase, Setting> {
|
||||
ColumnOrderings<String> get key => $composableBuilder(
|
||||
column: $table.key, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get partitionKey => $composableBuilder(
|
||||
column: $table.partitionKey,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DriftAny> get value => $composableBuilder(
|
||||
column: $table.value, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
@@ -446,6 +490,9 @@ class $SettingAnnotationComposer extends Composer<_$UserDatabase, Setting> {
|
||||
GeneratedColumn<String> get key =>
|
||||
$composableBuilder(column: $table.key, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get partitionKey => $composableBuilder(
|
||||
column: $table.partitionKey, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DriftAny> get value =>
|
||||
$composableBuilder(column: $table.value, builder: (column) => column);
|
||||
}
|
||||
@@ -474,21 +521,25 @@ class $SettingTableManager extends RootTableManager<
|
||||
$SettingAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> key = const Value.absent(),
|
||||
Value<String?> partitionKey = const Value.absent(),
|
||||
Value<DriftAny?> value = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
SettingCompanion(
|
||||
key: key,
|
||||
partitionKey: partitionKey,
|
||||
value: value,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String key,
|
||||
Value<String?> partitionKey = const Value.absent(),
|
||||
Value<DriftAny?> value = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
SettingCompanion.insert(
|
||||
key: key,
|
||||
partitionKey: partitionKey,
|
||||
value: value,
|
||||
rowid: rowid,
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
@@ -6,18 +7,45 @@ part 'engine_settings.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class EngineSettings extends GeckoEngineSettings {
|
||||
class EngineSettings extends GeckoEngineSettings with FastEquatable {
|
||||
@override
|
||||
bool get javascriptEnabled => super.javascriptEnabled!;
|
||||
@override
|
||||
TrackingProtectionPolicy get trackingProtectionPolicy =>
|
||||
super.trackingProtectionPolicy!;
|
||||
@override
|
||||
HttpsOnlyMode get httpsOnlyMode => super.httpsOnlyMode!;
|
||||
@override
|
||||
ColorScheme get preferredColorScheme => super.preferredColorScheme!;
|
||||
@override
|
||||
bool get globalPrivacyControlEnabled => super.globalPrivacyControlEnabled!;
|
||||
@override
|
||||
CookieBannerHandlingMode get cookieBannerHandlingMode =>
|
||||
super.cookieBannerHandlingMode!;
|
||||
@override
|
||||
CookieBannerHandlingMode get cookieBannerHandlingModePrivateBrowsing =>
|
||||
super.cookieBannerHandlingModePrivateBrowsing!;
|
||||
@override
|
||||
bool get cookieBannerHandlingGlobalRules =>
|
||||
super.cookieBannerHandlingGlobalRules!;
|
||||
@override
|
||||
bool get cookieBannerHandlingGlobalRulesSubFrames =>
|
||||
super.cookieBannerHandlingGlobalRulesSubFrames!;
|
||||
@override
|
||||
WebContentIsolationStrategy get webContentIsolationStrategy =>
|
||||
super.webContentIsolationStrategy!;
|
||||
|
||||
EngineSettings({
|
||||
super.javascriptEnabled,
|
||||
super.trackingProtectionPolicy,
|
||||
super.httpsOnlyMode,
|
||||
super.globalPrivacyControlEnabled,
|
||||
super.preferredColorScheme,
|
||||
super.cookieBannerHandlingMode,
|
||||
super.cookieBannerHandlingModePrivateBrowsing,
|
||||
super.cookieBannerHandlingGlobalRules,
|
||||
super.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
super.webContentIsolationStrategy,
|
||||
required super.javascriptEnabled,
|
||||
required super.trackingProtectionPolicy,
|
||||
required super.httpsOnlyMode,
|
||||
required super.globalPrivacyControlEnabled,
|
||||
required super.preferredColorScheme,
|
||||
required super.cookieBannerHandlingMode,
|
||||
required super.cookieBannerHandlingModePrivateBrowsing,
|
||||
required super.cookieBannerHandlingGlobalRules,
|
||||
required super.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
required super.webContentIsolationStrategy,
|
||||
});
|
||||
|
||||
EngineSettings.withDefaults({
|
||||
@@ -50,4 +78,26 @@ class EngineSettings extends GeckoEngineSettings {
|
||||
webContentIsolationStrategy: webContentIsolationStrategy ??
|
||||
WebContentIsolationStrategy.isolateHighValue,
|
||||
);
|
||||
|
||||
factory EngineSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$EngineSettingsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$EngineSettingsToJson(this);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
super.javascriptEnabled,
|
||||
super.trackingProtectionPolicy,
|
||||
super.httpsOnlyMode,
|
||||
super.globalPrivacyControlEnabled,
|
||||
super.preferredColorScheme,
|
||||
super.cookieBannerHandlingMode,
|
||||
super.cookieBannerHandlingModePrivateBrowsing,
|
||||
super.cookieBannerHandlingGlobalRules,
|
||||
super.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
super.webContentIsolationStrategy,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -225,22 +225,22 @@ Map<String, dynamic> _$EngineSettingsToJson(EngineSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'javascriptEnabled': instance.javascriptEnabled,
|
||||
'trackingProtectionPolicy':
|
||||
_$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy],
|
||||
'httpsOnlyMode': _$HttpsOnlyModeEnumMap[instance.httpsOnlyMode],
|
||||
'globalPrivacyControlEnabled': instance.globalPrivacyControlEnabled,
|
||||
_$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy]!,
|
||||
'httpsOnlyMode': _$HttpsOnlyModeEnumMap[instance.httpsOnlyMode]!,
|
||||
'preferredColorScheme':
|
||||
_$ColorSchemeEnumMap[instance.preferredColorScheme],
|
||||
_$ColorSchemeEnumMap[instance.preferredColorScheme]!,
|
||||
'globalPrivacyControlEnabled': instance.globalPrivacyControlEnabled,
|
||||
'cookieBannerHandlingMode':
|
||||
_$CookieBannerHandlingModeEnumMap[instance.cookieBannerHandlingMode],
|
||||
_$CookieBannerHandlingModeEnumMap[instance.cookieBannerHandlingMode]!,
|
||||
'cookieBannerHandlingModePrivateBrowsing':
|
||||
_$CookieBannerHandlingModeEnumMap[
|
||||
instance.cookieBannerHandlingModePrivateBrowsing],
|
||||
instance.cookieBannerHandlingModePrivateBrowsing]!,
|
||||
'cookieBannerHandlingGlobalRules':
|
||||
instance.cookieBannerHandlingGlobalRules,
|
||||
'cookieBannerHandlingGlobalRulesSubFrames':
|
||||
instance.cookieBannerHandlingGlobalRulesSubFrames,
|
||||
'webContentIsolationStrategy': _$WebContentIsolationStrategyEnumMap[
|
||||
instance.webContentIsolationStrategy],
|
||||
instance.webContentIsolationStrategy]!,
|
||||
};
|
||||
|
||||
const _$TrackingProtectionPolicyEnumMap = {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'general_settings.g.dart';
|
||||
|
||||
enum DeleteBrowsingDataType {
|
||||
tabs('Open tabs'),
|
||||
history('Browsing history'),
|
||||
cookies('Cookies and site data', 'You’ll be logged out of most sites'),
|
||||
cache('Cached images and files', 'Frees up storage space'),
|
||||
permissions('Site permissions'),
|
||||
downloads('Downloads');
|
||||
|
||||
final String title;
|
||||
final String? description;
|
||||
|
||||
const DeleteBrowsingDataType(this.title, [this.description]);
|
||||
}
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class GeneralSettings with FastEquatable {
|
||||
final ThemeMode themeMode;
|
||||
final bool enableReadability;
|
||||
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
||||
|
||||
GeneralSettings({
|
||||
required this.themeMode,
|
||||
required this.enableReadability,
|
||||
required this.deleteBrowsingDataOnQuit,
|
||||
});
|
||||
|
||||
GeneralSettings.withDefaults({
|
||||
ThemeMode? themeMode,
|
||||
bool? enableReadability,
|
||||
this.deleteBrowsingDataOnQuit,
|
||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||
enableReadability = enableReadability ?? true;
|
||||
|
||||
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$GeneralSettingsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$GeneralSettingsToJson(this);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
themeMode,
|
||||
enableReadability,
|
||||
deleteBrowsingDataOnQuit,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'general_settings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$GeneralSettingsCWProxy {
|
||||
GeneralSettings themeMode(ThemeMode themeMode);
|
||||
|
||||
GeneralSettings enableReadability(bool enableReadability);
|
||||
|
||||
GeneralSettings deleteBrowsingDataOnQuit(
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// GeneralSettings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
GeneralSettings call({
|
||||
ThemeMode themeMode,
|
||||
bool enableReadability,
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfGeneralSettings.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfGeneralSettings.copyWith.fieldName(...)`
|
||||
class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
const _$GeneralSettingsCWProxyImpl(this._value);
|
||||
|
||||
final GeneralSettings _value;
|
||||
|
||||
@override
|
||||
GeneralSettings themeMode(ThemeMode themeMode) => this(themeMode: themeMode);
|
||||
|
||||
@override
|
||||
GeneralSettings enableReadability(bool enableReadability) =>
|
||||
this(enableReadability: enableReadability);
|
||||
|
||||
@override
|
||||
GeneralSettings deleteBrowsingDataOnQuit(
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit) =>
|
||||
this(deleteBrowsingDataOnQuit: deleteBrowsingDataOnQuit);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// GeneralSettings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
GeneralSettings call({
|
||||
Object? themeMode = const $CopyWithPlaceholder(),
|
||||
Object? enableReadability = const $CopyWithPlaceholder(),
|
||||
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return GeneralSettings(
|
||||
themeMode: themeMode == const $CopyWithPlaceholder()
|
||||
? _value.themeMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: themeMode as ThemeMode,
|
||||
enableReadability: enableReadability == const $CopyWithPlaceholder()
|
||||
? _value.enableReadability
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: enableReadability as bool,
|
||||
deleteBrowsingDataOnQuit:
|
||||
deleteBrowsingDataOnQuit == const $CopyWithPlaceholder()
|
||||
? _value.deleteBrowsingDataOnQuit
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: deleteBrowsingDataOnQuit as Set<DeleteBrowsingDataType>?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $GeneralSettingsCopyWith on GeneralSettings {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfGeneralSettings.copyWith(...)` or like so:`instanceOfGeneralSettings.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$GeneralSettingsCWProxy get copyWith => _$GeneralSettingsCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
|
||||
GeneralSettings.withDefaults(
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||
enableReadability: json['enableReadability'] as bool?,
|
||||
deleteBrowsingDataOnQuit:
|
||||
(json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
||||
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
|
||||
.toSet(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'enableReadability': instance.enableReadability,
|
||||
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
|
||||
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
|
||||
.toList(),
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
ThemeMode.light: 'light',
|
||||
ThemeMode.dark: 'dark',
|
||||
};
|
||||
|
||||
const _$DeleteBrowsingDataTypeEnumMap = {
|
||||
DeleteBrowsingDataType.tabs: 'tabs',
|
||||
DeleteBrowsingDataType.history: 'history',
|
||||
DeleteBrowsingDataType.cookies: 'cookies',
|
||||
DeleteBrowsingDataType.cache: 'cache',
|
||||
DeleteBrowsingDataType.permissions: 'permissions',
|
||||
DeleteBrowsingDataType.downloads: 'downloads',
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'settings.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class Settings with FastEquatable {
|
||||
final bool incognitoMode;
|
||||
final bool enableJavascript;
|
||||
final bool blockHttpProtocol;
|
||||
final ThemeMode themeMode;
|
||||
final bool enableReadability;
|
||||
|
||||
Settings({
|
||||
required this.incognitoMode,
|
||||
required this.enableJavascript,
|
||||
required this.blockHttpProtocol,
|
||||
required this.themeMode,
|
||||
required this.enableReadability,
|
||||
});
|
||||
|
||||
Settings.withDefaults({
|
||||
bool? incognitoMode,
|
||||
bool? enableJavascript,
|
||||
bool? blockHttpProtocol,
|
||||
ThemeMode? themeMode,
|
||||
bool? enableReadability,
|
||||
}) : incognitoMode = incognitoMode ?? true,
|
||||
enableJavascript = enableJavascript ?? true,
|
||||
blockHttpProtocol = blockHttpProtocol ?? false,
|
||||
themeMode = themeMode ?? ThemeMode.dark,
|
||||
enableReadability = enableReadability ?? true;
|
||||
|
||||
factory Settings.fromJson(Map<String, dynamic> json) =>
|
||||
_$SettingsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SettingsToJson(this);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
incognitoMode,
|
||||
enableJavascript,
|
||||
blockHttpProtocol,
|
||||
themeMode,
|
||||
enableReadability,
|
||||
];
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'settings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$SettingsCWProxy {
|
||||
Settings incognitoMode(bool incognitoMode);
|
||||
|
||||
Settings enableJavascript(bool enableJavascript);
|
||||
|
||||
Settings blockHttpProtocol(bool blockHttpProtocol);
|
||||
|
||||
Settings themeMode(ThemeMode themeMode);
|
||||
|
||||
Settings enableReadability(bool enableReadability);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Settings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// Settings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
Settings call({
|
||||
bool incognitoMode,
|
||||
bool enableJavascript,
|
||||
bool blockHttpProtocol,
|
||||
ThemeMode themeMode,
|
||||
bool enableReadability,
|
||||
});
|
||||
}
|
||||
|
||||
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfSettings.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfSettings.copyWith.fieldName(...)`
|
||||
class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
const _$SettingsCWProxyImpl(this._value);
|
||||
|
||||
final Settings _value;
|
||||
|
||||
@override
|
||||
Settings incognitoMode(bool incognitoMode) =>
|
||||
this(incognitoMode: incognitoMode);
|
||||
|
||||
@override
|
||||
Settings enableJavascript(bool enableJavascript) =>
|
||||
this(enableJavascript: enableJavascript);
|
||||
|
||||
@override
|
||||
Settings blockHttpProtocol(bool blockHttpProtocol) =>
|
||||
this(blockHttpProtocol: blockHttpProtocol);
|
||||
|
||||
@override
|
||||
Settings themeMode(ThemeMode themeMode) => this(themeMode: themeMode);
|
||||
|
||||
@override
|
||||
Settings enableReadability(bool enableReadability) =>
|
||||
this(enableReadability: enableReadability);
|
||||
|
||||
@override
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Settings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
/// ```dart
|
||||
/// Settings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
Settings call({
|
||||
Object? incognitoMode = const $CopyWithPlaceholder(),
|
||||
Object? enableJavascript = const $CopyWithPlaceholder(),
|
||||
Object? blockHttpProtocol = const $CopyWithPlaceholder(),
|
||||
Object? themeMode = const $CopyWithPlaceholder(),
|
||||
Object? enableReadability = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return Settings(
|
||||
incognitoMode: incognitoMode == const $CopyWithPlaceholder()
|
||||
? _value.incognitoMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: incognitoMode as bool,
|
||||
enableJavascript: enableJavascript == const $CopyWithPlaceholder()
|
||||
? _value.enableJavascript
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: enableJavascript as bool,
|
||||
blockHttpProtocol: blockHttpProtocol == const $CopyWithPlaceholder()
|
||||
? _value.blockHttpProtocol
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: blockHttpProtocol as bool,
|
||||
themeMode: themeMode == const $CopyWithPlaceholder()
|
||||
? _value.themeMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: themeMode as ThemeMode,
|
||||
enableReadability: enableReadability == const $CopyWithPlaceholder()
|
||||
? _value.enableReadability
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: enableReadability as bool,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $SettingsCopyWith on Settings {
|
||||
/// Returns a callable class that can be used as follows: `instanceOfSettings.copyWith(...)` or like so:`instanceOfSettings.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$SettingsCWProxy get copyWith => _$SettingsCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings.withDefaults(
|
||||
incognitoMode: json['incognitoMode'] as bool?,
|
||||
enableJavascript: json['enableJavascript'] as bool?,
|
||||
blockHttpProtocol: json['blockHttpProtocol'] as bool?,
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||
enableReadability: json['enableReadability'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
'incognitoMode': instance.incognitoMode,
|
||||
'enableJavascript': instance.enableJavascript,
|
||||
'blockHttpProtocol': instance.blockHttpProtocol,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'enableReadability': instance.enableReadability,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
ThemeMode.light: 'light',
|
||||
ThemeMode.dark: 'dark',
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:lensai/features/user/data/providers.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:pocketbase/pocketbase.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -47,3 +48,11 @@ PocketBase pocketBase(Ref ref) {
|
||||
|
||||
return PocketBase('http://192.168.2.104:8090', authStore: authStore);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
bool incognitoModeEnabled(Ref ref) {
|
||||
return ref.watch(
|
||||
generalSettingsRepositoryProvider
|
||||
.select((value) => value.deleteBrowsingDataOnQuit != null),
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user