This commit is contained in:
Fabian Freund
2025-02-03 15:09:04 +01:00
parent 36acd46318
commit 025ecaf2e3
132 changed files with 4431 additions and 1644 deletions
@@ -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', 'Youll 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),
);
}
@@ -90,5 +90,23 @@ final pocketBaseProvider = Provider<PocketBase>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef PocketBaseRef = ProviderRef<PocketBase>;
String _$incognitoModeEnabledHash() =>
r'3968c2d4945be09a0bc08ce0d70b0390187c44e8';
/// See also [incognitoModeEnabled].
@ProviderFor(incognitoModeEnabled)
final incognitoModeEnabledProvider = AutoDisposeProvider<bool>.internal(
incognitoModeEnabled,
name: r'incognitoModeEnabledProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$incognitoModeEnabledHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef IncognitoModeEnabledRef = AutoDisposeProviderRef<bool>;
// 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,7 +1,6 @@
import 'dart:typed_data';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/user/data/database/database.dart';
import 'package:lensai/features/user/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -9,29 +8,31 @@ part 'cache.g.dart';
@Riverpod(keepAlive: true)
class CacheRepository extends _$CacheRepository {
late UserDatabase _db;
Future<void> clearCache() {
return _db.cacheDao.clearIconCache();
return ref.read(userDatabaseProvider).cacheDao.clearIconCache();
}
Future<void> cacheIcon(Uri url, Uint8List bytes) {
return _db.cacheDao.cacheIcon(url.origin, bytes);
return ref.read(userDatabaseProvider).cacheDao.cacheIcon(url.origin, bytes);
}
Future<Uint8List?> getCachedIcon(String origin) {
return _db.cacheDao.getCachedIcon(origin).getSingleOrNull();
return ref
.read(userDatabaseProvider)
.cacheDao
.getCachedIcon(origin)
.getSingleOrNull();
}
@override
void build() {
final eventService = ref.watch(eventServiceProvider);
_db = ref.watch(userDatabaseProvider);
final db = ref.watch(userDatabaseProvider);
final sub = eventService.iconUpdateEvents.listen((event) async {
if (Uri.tryParse(event.url) case final Uri url) {
await _db.cacheDao.cacheIcon(url.origin, event.bytes);
await db.cacheDao.cacheIcon(url.origin, event.bytes);
}
});
@@ -6,7 +6,7 @@ part of 'cache.dart';
// RiverpodGenerator
// **************************************************************************
String _$cacheRepositoryHash() => r'37b25ff308b9f25575625984e55cb02f9c0293f0';
String _$cacheRepositoryHash() => r'548b7cba9a23c21bba39f0918d7d90acaaf8d8e9';
/// See also [CacheRepository].
@ProviderFor(CacheRepository)
@@ -0,0 +1,78 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:lensai/features/user/data/database/database.dart';
import 'package:lensai/features/user/data/models/engine_settings.dart';
import 'package:lensai/features/user/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'engine_settings.g.dart';
typedef UpdateEngineSettingsFunc = EngineSettings Function(
EngineSettings currentSettings,
);
@Riverpod(keepAlive: true)
class EngineSettingsRepository extends _$EngineSettingsRepository {
final _partitionKey = 'engine';
Future<void> updateSettings(UpdateEngineSettingsFunc updateWithCurrent) {
final oldJson = state.toJson();
final newJson = updateWithCurrent(state).toJson();
return ref.read(userDatabaseProvider).transaction(() async {
for (final MapEntry(:key, :value) in newJson.entries) {
if (oldJson[key] != value) {
await ref
.read(userDatabaseProvider)
.settingDao
.updateSetting(key, _partitionKey, value);
}
}
});
}
@override
EngineSettings build() {
final db = ref.watch(userDatabaseProvider);
final watchSub = db.settingDao
.allSettingsOfPartitionKey(_partitionKey)
.watch()
.listen((entries) {
final settings = Map.fromEntries(entries);
state = EngineSettings.fromJson({
'incognitoMode': settings['incognitoMode']
?.readAs(DriftSqlType.bool, db.typeMapping),
'javascriptEnabled': settings['javascriptEnabled']
?.readAs(DriftSqlType.bool, db.typeMapping),
'trackingProtectionPolicy': settings['trackingProtectionPolicy']
?.readAs(DriftSqlType.string, db.typeMapping),
'httpsOnlyMode': settings['httpsOnlyMode']
?.readAs(DriftSqlType.string, db.typeMapping),
'globalPrivacyControlEnabled': settings['globalPrivacyControlEnabled']
?.readAs(DriftSqlType.bool, db.typeMapping),
'cookieBannerHandlingMode': settings['cookieBannerHandlingMode']
?.readAs(DriftSqlType.string, db.typeMapping),
'cookieBannerHandlingModePrivateBrowsing':
settings['cookieBannerHandlingModePrivateBrowsing']
?.readAs(DriftSqlType.string, db.typeMapping),
'cookieBannerHandlingGlobalRules':
settings['cookieBannerHandlingGlobalRules']
?.readAs(DriftSqlType.bool, db.typeMapping),
'cookieBannerHandlingGlobalRulesSubFrames':
settings['cookieBannerHandlingGlobalRulesSubFrames']
?.readAs(DriftSqlType.bool, db.typeMapping),
'webContentIsolationStrategy': settings['webContentIsolationStrategy']
?.readAs(DriftSqlType.string, db.typeMapping),
});
});
ref.onDispose(() {
unawaited(watchSub.cancel());
});
return EngineSettings.withDefaults();
}
}
@@ -1,27 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings.dart';
part of 'engine_settings.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$settingsRepositoryHash() =>
r'7e3e032957f5092aa3d445d2b7b05a882e3cfd20';
String _$engineSettingsRepositoryHash() =>
r'113617b3a99377bbb83347cfcb6d432abaae6f71';
/// See also [SettingsRepository].
@ProviderFor(SettingsRepository)
final settingsRepositoryProvider =
NotifierProvider<SettingsRepository, Settings>.internal(
SettingsRepository.new,
name: r'settingsRepositoryProvider',
/// See also [EngineSettingsRepository].
@ProviderFor(EngineSettingsRepository)
final engineSettingsRepositoryProvider =
NotifierProvider<EngineSettingsRepository, EngineSettings>.internal(
EngineSettingsRepository.new,
name: r'engineSettingsRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$settingsRepositoryHash,
: _$engineSettingsRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SettingsRepository = Notifier<Settings>;
typedef _$EngineSettingsRepository = Notifier<EngineSettings>;
// 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,80 @@
import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/user/data/models/general_settings.dart';
import 'package:lensai/features/user/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'general_settings.g.dart';
typedef UpdateGeneralSettingsFunc = GeneralSettings Function(
GeneralSettings currentSettings,
);
@Riverpod(keepAlive: true)
class GeneralSettingsRepository extends _$GeneralSettingsRepository {
final _partitionKey = 'general';
GeneralSettings _deserializeSettings(
List<MapEntry<String, DriftAny?>> entries,
) {
final settings = Map.fromEntries(entries);
final db = ref.read(userDatabaseProvider);
return GeneralSettings.fromJson({
'themeMode':
settings['themeMode']?.readAs(DriftSqlType.string, db.typeMapping),
'enableReadability': settings['enableReadability']
?.readAs(DriftSqlType.bool, db.typeMapping),
'deleteBrowsingDataOnQuit': settings['deleteBrowsingDataOnQuit']
?.readAs(DriftSqlType.string, db.typeMapping)
.mapNotNull(jsonDecode),
});
}
//Eager fetch, when up to date settings are required
Future<GeneralSettings> fetch() async {
return ref
.read(userDatabaseProvider)
.settingDao
.allSettingsOfPartitionKey(_partitionKey)
.get()
.then(_deserializeSettings);
}
Future<void> updateSettings(UpdateGeneralSettingsFunc updateWithCurrent) {
final oldJson = state.toJson();
final newJson = updateWithCurrent(state).toJson();
final db = ref.read(userDatabaseProvider);
return db.transaction(() async {
for (final MapEntry(:key, :value) in newJson.entries) {
if (oldJson[key] != value) {
await db.settingDao.updateSetting(key, _partitionKey, value);
}
}
});
}
@override
GeneralSettings build() {
final db = ref.watch(userDatabaseProvider);
final watchSub =
db.settingDao.allSettingsOfPartitionKey(_partitionKey).watch().listen(
(event) {
state = _deserializeSettings(event);
},
);
ref.onDispose(() {
unawaited(watchSub.cancel());
});
return GeneralSettings.withDefaults();
}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'general_settings.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$generalSettingsRepositoryHash() =>
r'd21cee982ed471dff5ba11415e5557073bd2fca3';
/// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository)
final generalSettingsRepositoryProvider =
NotifierProvider<GeneralSettingsRepository, GeneralSettings>.internal(
GeneralSettingsRepository.new,
name: r'generalSettingsRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$generalSettingsRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$GeneralSettingsRepository = Notifier<GeneralSettings>;
// 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,57 +0,0 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:lensai/features/user/data/database/database.dart';
import 'package:lensai/features/user/data/models/settings.dart';
import 'package:lensai/features/user/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'settings.g.dart';
typedef UpdateSettingsFunc = Settings Function(Settings currentSettings);
@Riverpod(keepAlive: true)
class SettingsRepository extends _$SettingsRepository {
late UserDatabase _db;
Future<void> updateSettings(UpdateSettingsFunc updateWithCurrent) {
final oldJson = state.toJson();
final newJson = updateWithCurrent(state).toJson();
return _db.transaction(() async {
for (final MapEntry(:key, :value) in newJson.entries) {
if (oldJson[key] != value) {
await _db.settingDao.updateSetting(key, value);
}
}
});
}
@override
Settings build() {
_db = ref.watch(userDatabaseProvider);
final watchSub = _db.settingDao.allSettings().watch().listen((entries) {
final settings = Map.fromEntries(entries);
state = Settings.fromJson({
'incognitoMode': settings['incognitoMode']
?.readAs(DriftSqlType.bool, _db.typeMapping),
'enableJavascript': settings['enableJavascript']
?.readAs(DriftSqlType.bool, _db.typeMapping),
'blockHttpProtocol': settings['blockHttpProtocol']
?.readAs(DriftSqlType.bool, _db.typeMapping),
'themeMode':
settings['themeMode']?.readAs(DriftSqlType.string, _db.typeMapping),
'enableReadability': settings['enableReadability']
?.readAs(DriftSqlType.bool, _db.typeMapping),
});
});
ref.onDispose(() {
unawaited(watchSub.cancel());
});
return Settings.withDefaults();
}
}
@@ -0,0 +1,61 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:local_auth/local_auth.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'local_authentication.g.dart';
@Riverpod(keepAlive: true)
class LocalAuthenticationService extends _$LocalAuthenticationService {
final _auth = LocalAuthentication();
final _cache = <String, (DateTime, ContainerAuthSettings)>{};
bool _cacheAuth(String authKey) {
final auth = _cache[authKey];
if (auth != null && auth.$2.lockTimeout != null) {
return DateTime.now().difference(auth.$1) < auth.$2.lockTimeout!;
}
return false;
}
void evictCacheOnBackground() {
_cache.removeWhere(
(key, value) => value.$2.lockOnAppBackground,
);
}
Future<bool> authenticate({
required String authKey,
required String localizedReason,
ContainerAuthSettings? settings,
bool useAuthCache = false,
}) async {
try {
var result = useAuthCache && _cacheAuth(authKey);
if (!result) {
result = await _auth.authenticate(
localizedReason: localizedReason,
);
}
if (result && settings != null) {
_cache[authKey] = (DateTime.now(), settings);
}
return result;
} on PlatformException catch (e, s) {
logger.e('Could not authenticate', error: e, stackTrace: s);
return false;
}
}
@override
Future<bool> build() {
return _auth.canCheckBiometrics;
}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'local_authentication.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$localAuthenticationServiceHash() =>
r'4893be7a11833d77a575f7aa248a9ba65d6717da';
/// See also [LocalAuthenticationService].
@ProviderFor(LocalAuthenticationService)
final localAuthenticationServiceProvider =
AsyncNotifierProvider<LocalAuthenticationService, bool>.internal(
LocalAuthenticationService.new,
name: r'localAuthenticationServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$localAuthenticationServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$LocalAuthenticationService = AsyncNotifier<bool>;
// 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