prepare for multiple apps

This commit is contained in:
Fabian Freund
2026-04-06 12:23:11 +02:00
parent bd1600e8dc
commit 5afc323f04
904 changed files with 29 additions and 29 deletions
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:drift/drift.dart';
import 'package:weblibre/data/database/extensions/database_table_size.dart';
import 'package:weblibre/features/user/data/database/daos/cache.drift.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
@DriftAccessor()
class CacheDao extends DatabaseAccessor<UserDatabase> with $CacheDaoMixin {
CacheDao(super.attachedDatabase);
SingleSelectable<double> getIconCacheSize() {
return db.tableSize(db.iconCache);
}
Future<int> clearIconCache() {
return db.iconCache.deleteAll();
}
SingleOrNullSelectable<Uint8List?> getCachedIcon(String origin) {
final query = selectOnly(db.iconCache)
..addColumns([db.iconCache.iconData])
..where(db.iconCache.origin.equals(origin));
return query.map((row) => row.read(db.iconCache.iconData));
}
Future<int> cacheIcon(String origin, Uint8List bytes) {
return db.iconCache.insertOne(
IconCacheCompanion.insert(
origin: origin,
iconData: bytes,
fetchDate: DateTime.now(),
),
onConflict: DoUpdate(
(old) => IconCacheCompanion(
iconData: Value(bytes),
fetchDate: Value(DateTime.now()),
),
),
);
}
}
@@ -0,0 +1,13 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:weblibre/features/user/data/database/database.dart' as i1;
mixin $CacheDaoMixin on i0.DatabaseAccessor<i1.UserDatabase> {
CacheDaoManager get managers => CacheDaoManager(this);
}
class CacheDaoManager {
final $CacheDaoMixin _db;
CacheDaoManager(this._db);
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:drift/drift.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
@DriftAccessor()
class OnboardingDao extends DatabaseAccessor<UserDatabase> {
OnboardingDao(super.attachedDatabase);
SingleOrNullSelectable<int?> getLastRevision() {
final maxRevision = db.onboarding.revision.max();
final query = selectOnly(db.onboarding)..addColumns([maxRevision]);
return query.map((row) => row.read(maxRevision));
}
Future<void> pushRevision(int revision, DateTime completionDate) {
return db.onboarding.insertOne(
OnboardingCompanion.insert(
revision: revision,
completionDate: completionDate,
),
mode: InsertMode.insertOrReplace,
);
}
}
@@ -0,0 +1,13 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:weblibre/features/user/data/database/database.dart' as i1;
mixin $OnboardingDaoMixin on i0.DatabaseAccessor<i1.UserDatabase> {
OnboardingDaoManager get managers => OnboardingDaoManager(this);
}
class OnboardingDaoManager {
final $OnboardingDaoMixin _db;
OnboardingDaoManager(this._db);
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/user/data/database/daos/setting.drift.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
@DriftAccessor()
class SettingDao extends DatabaseAccessor<UserDatabase> with $SettingDaoMixin {
SettingDao(super.attachedDatabase);
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(
(old) => SettingCompanion(
partitionKey: Value(partitionKey),
value: Value(driftvalue),
),
),
);
}
Selectable<MapEntry<String, DriftAny?>> getAllSettingsOfPartitionKey(
String? partitionKey,
) {
final query = db.setting.select()
..where((r) => r.partitionKey.equalsNullable(partitionKey));
return query.map((row) => MapEntry(row.key, row.value));
}
}
@@ -0,0 +1,13 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:weblibre/features/user/data/database/database.dart' as i1;
mixin $SettingDaoMixin on i0.DatabaseAccessor<i1.UserDatabase> {
SettingDaoManager get managers => SettingDaoManager(this);
}
class SettingDaoManager {
final $SettingDaoMixin _db;
SettingDaoManager(this._db);
}
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:drift/drift.dart';
import 'package:weblibre/features/user/data/database/daos/toolbar_button_config.drift.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
@DriftAccessor()
class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
with $ToolbarButtonConfigDaoMixin {
ToolbarButtonConfigDao(super.attachedDatabase);
Selectable<ToolbarButtonConfig> selectAll() =>
db.toolbarButtonConfigs.select()
..orderBy([(t) => OrderingTerm.asc(t.orderKey)]);
Stream<List<ToolbarButtonConfig>> watchAll() => selectAll().watch();
Future<List<ToolbarButtonConfig>> getAll() => selectAll().get();
Future<void> upsert(ToolbarButtonConfig config) =>
into(db.toolbarButtonConfigs).insertOnConflictUpdate(config);
Future<void> assignOrderKey(String buttonId, {required String orderKey}) =>
(update(db.toolbarButtonConfigs)
..where((t) => t.buttonId.equals(buttonId)))
.write(ToolbarButtonConfigsCompanion(orderKey: Value(orderKey)));
Future<void> assignVisibility(String buttonId, {required bool visible}) =>
(update(db.toolbarButtonConfigs)
..where((t) => t.buttonId.equals(buttonId)))
.write(ToolbarButtonConfigsCompanion(isVisible: Value(visible)));
Future<void> assignFallback(String buttonId, String? fallbackId) =>
(update(db.toolbarButtonConfigs)
..where((t) => t.buttonId.equals(buttonId)))
.write(ToolbarButtonConfigsCompanion(fallbackId: Value(fallbackId)));
Future<void> replaceAll(List<ToolbarButtonConfig> configs) =>
transaction(() async {
await delete(db.toolbarButtonConfigs).go();
await _insertWithDeferredFallbacks(configs);
});
Future<void> seedMissing(
List<({String buttonId, bool defaultVisible, String? defaultFallback})>
defaults,
) async {
final existing = await getAll();
final existingIds = {for (final r in existing) r.buttonId};
final missing = defaults
.where((d) => !existingIds.contains(d.buttonId))
.toList();
if (missing.isEmpty) return;
await transaction(() async {
final inserted = <ToolbarButtonConfig>[];
for (final def in missing) {
final orderKey = await generateTrailingOrderKey().getSingle();
inserted.add(
ToolbarButtonConfig(
buttonId: def.buttonId,
orderKey: orderKey,
isVisible: def.defaultVisible,
fallbackId: def.defaultFallback,
),
);
await into(db.toolbarButtonConfigs).insert(
ToolbarButtonConfig(
buttonId: def.buttonId,
orderKey: orderKey,
isVisible: def.defaultVisible,
),
);
}
await _assignFallbacks(inserted);
});
}
SingleSelectable<String> generateLeadingOrderKey({int bucket = 0}) =>
db.definitionsDrift.toolbarLeadingOrderKey(bucket: bucket);
SingleSelectable<String> generateTrailingOrderKey({int bucket = 0}) =>
db.definitionsDrift.toolbarTrailingOrderKey(bucket: bucket);
SingleOrNullSelectable<String> generateOrderKeyAfterButtonId(
String buttonId,
) => db.definitionsDrift.toolbarOrderKeyAfterButton(buttonId: buttonId);
SingleSelectable<String> generateOrderKeyBeforeButtonId(String buttonId) =>
db.definitionsDrift.toolbarOrderKeyBeforeButton(buttonId: buttonId);
Future<void> _insertWithDeferredFallbacks(
List<ToolbarButtonConfig> configs,
) async {
for (final config in configs) {
await into(db.toolbarButtonConfigs).insert(
ToolbarButtonConfig(
buttonId: config.buttonId,
orderKey: config.orderKey,
isVisible: config.isVisible,
),
);
}
await _assignFallbacks(configs);
}
Future<void> _assignFallbacks(List<ToolbarButtonConfig> configs) async {
for (final config in configs) {
if (config.fallbackId == null) continue;
await assignFallback(config.buttonId, config.fallbackId);
}
}
}
@@ -0,0 +1,14 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:weblibre/features/user/data/database/database.dart' as i1;
mixin $ToolbarButtonConfigDaoMixin on i0.DatabaseAccessor<i1.UserDatabase> {
ToolbarButtonConfigDaoManager get managers =>
ToolbarButtonConfigDaoManager(this);
}
class ToolbarButtonConfigDaoManager {
final $ToolbarButtonConfigDaoMixin _db;
ToolbarButtonConfigDaoManager(this._db);
}
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:drift/drift.dart';
import 'package:drift/internal/versioned_schema.dart';
import 'package:drift_dev/api/migrations_native.dart';
import 'package:flutter/foundation.dart';
import 'package:weblibre/features/user/data/database/daos/cache.dart';
import 'package:weblibre/features/user/data/database/daos/onboarding.dart';
import 'package:weblibre/features/user/data/database/daos/setting.dart';
import 'package:weblibre/features/user/data/database/daos/toolbar_button_config.dart';
import 'package:weblibre/features/user/data/database/database.drift.dart';
import 'package:weblibre/features/user/data/database/database.steps.dart';
@DriftDatabase(
include: {'definitions.drift'},
daos: [SettingDao, CacheDao, OnboardingDao, ToolbarButtonConfigDao],
)
class UserDatabase extends $UserDatabase {
@override
final int schemaVersion = 3;
@override
MigrationStrategy get migration => MigrationStrategy(
beforeOpen: (details) async {
if (kDebugMode) {
// This check pulls in a fair amount of code that's not needed
// anywhere else, so we recommend only doing it in debug builds.
await validateDatabaseSchema();
}
await customStatement('PRAGMA foreign_keys = ON;');
await onAfterOpen?.call(this);
},
onUpgrade: (m, from, to) async {
// Following the advice from https://drift.simonbinder.eu/Migrations/api/#general-tips
await customStatement('PRAGMA foreign_keys = OFF');
await transaction(
() => VersionedSchema.runMigrationSteps(
migrator: m,
from: from,
to: to,
steps: _upgrade,
),
);
if (kDebugMode) {
final wrongForeignKeys = await customSelect(
'PRAGMA foreign_key_check',
).get();
assert(
wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}',
);
}
await customStatement('PRAGMA foreign_keys = ON');
},
);
UserDatabase(super.e, {this.onAfterOpen});
final Future<void> Function(UserDatabase db)? onAfterOpen;
static final _upgrade = migrationSteps(
from1To2: (m, schema) async {
await m.createTable(schema.riverpod);
},
from2To3: (m, schema) async {
await m.createTable(schema.toolbarButtonConfigs);
await m.createIndex(schema.idxToolbarOrderKey);
},
);
}
@@ -0,0 +1,108 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:weblibre/features/user/data/database/definitions.drift.dart'
as i1;
import 'package:weblibre/features/user/data/database/daos/setting.dart' as i2;
import 'package:weblibre/features/user/data/database/database.dart' as i3;
import 'package:weblibre/features/user/data/database/daos/cache.dart' as i4;
import 'package:weblibre/features/user/data/database/daos/onboarding.dart'
as i5;
import 'package:weblibre/features/user/data/database/daos/toolbar_button_config.dart'
as i6;
import 'package:drift/internal/modular.dart' as i7;
import 'package:sqlite3/common.dart' as i8;
abstract class $UserDatabase extends i0.GeneratedDatabase {
$UserDatabase(i0.QueryExecutor e) : super(e);
$UserDatabaseManager get managers => $UserDatabaseManager(this);
late final i1.Setting setting = i1.Setting(this);
late final i1.IconCache iconCache = i1.IconCache(this);
late final i1.Onboarding onboarding = i1.Onboarding(this);
late final i1.Riverpod riverpod = i1.Riverpod(this);
late final i1.ToolbarButtonConfigs toolbarButtonConfigs =
i1.ToolbarButtonConfigs(this);
late final i2.SettingDao settingDao = i2.SettingDao(this as i3.UserDatabase);
late final i4.CacheDao cacheDao = i4.CacheDao(this as i3.UserDatabase);
late final i5.OnboardingDao onboardingDao = i5.OnboardingDao(
this as i3.UserDatabase,
);
late final i6.ToolbarButtonConfigDao toolbarButtonConfigDao =
i6.ToolbarButtonConfigDao(this as i3.UserDatabase);
i1.DefinitionsDrift get definitionsDrift => i7.ReadDatabaseContainer(
this,
).accessor<i1.DefinitionsDrift>(i1.DefinitionsDrift.new);
@override
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
@override
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [
setting,
iconCache,
onboarding,
riverpod,
toolbarButtonConfigs,
i1.idxToolbarOrderKey,
];
}
class $UserDatabaseManager {
final $UserDatabase _db;
$UserDatabaseManager(this._db);
i1.$SettingTableManager get setting =>
i1.$SettingTableManager(_db, _db.setting);
i1.$IconCacheTableManager get iconCache =>
i1.$IconCacheTableManager(_db, _db.iconCache);
i1.$OnboardingTableManager get onboarding =>
i1.$OnboardingTableManager(_db, _db.onboarding);
i1.$RiverpodTableManager get riverpod =>
i1.$RiverpodTableManager(_db, _db.riverpod);
i1.$ToolbarButtonConfigsTableManager get toolbarButtonConfigs =>
i1.$ToolbarButtonConfigsTableManager(_db, _db.toolbarButtonConfigs);
}
extension DefineFunctions on i8.CommonDatabase {
void defineFunctions({
required String Function(int, String?) lexoRankNext,
required String Function(int, String?) lexoRankPrevious,
required String Function(String?, String?) lexoRankReorderAfter,
required String Function(String?, String?) lexoRankReorderBefore,
}) {
createFunction(
functionName: 'lexo_rank_next',
argumentCount: const i8.AllowedArgumentCount(2),
function: (args) {
final arg0 = args[0] as int;
final arg1 = args[1] as String?;
return lexoRankNext(arg0, arg1);
},
);
createFunction(
functionName: 'lexo_rank_previous',
argumentCount: const i8.AllowedArgumentCount(2),
function: (args) {
final arg0 = args[0] as int;
final arg1 = args[1] as String?;
return lexoRankPrevious(arg0, arg1);
},
);
createFunction(
functionName: 'lexo_rank_reorder_after',
argumentCount: const i8.AllowedArgumentCount(2),
function: (args) {
final arg0 = args[0] as String?;
final arg1 = args[1] as String?;
return lexoRankReorderAfter(arg0, arg1);
},
);
createFunction(
functionName: 'lexo_rank_reorder_before',
argumentCount: const i8.AllowedArgumentCount(2),
function: (args) {
final arg0 = args[0] as String?;
final arg1 = args[1] as String?;
return lexoRankReorderBefore(arg0, arg1);
},
);
}
}
@@ -0,0 +1,341 @@
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'dart:typed_data' as i2;
import 'package:drift/drift.dart'; // GENERATED BY drift_dev, DO NOT MODIFY.
// ignore_for_file: type=lint,unused_import
//
final class Schema2 extends i0.VersionedSchema {
Schema2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
setting,
iconCache,
onboarding,
riverpod,
];
late final Shape0 setting = Shape0(
source: i0.VersionedTable(
entityName: 'setting',
withoutRowId: false,
isStrict: true,
tableConstraints: [],
columns: [_column_0, _column_1, _column_2],
attachedDatabase: database,
),
alias: null,
);
late final Shape1 iconCache = Shape1(
source: i0.VersionedTable(
entityName: 'icon_cache',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_3, _column_4, _column_5],
attachedDatabase: database,
),
alias: null,
);
late final Shape2 onboarding = Shape2(
source: i0.VersionedTable(
entityName: 'onboarding',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_6, _column_7],
attachedDatabase: database,
),
alias: null,
);
late final Shape3 riverpod = Shape3(
source: i0.VersionedTable(
entityName: 'riverpod',
withoutRowId: true,
isStrict: false,
tableConstraints: [],
columns: [_column_0, _column_8, _column_9, _column_10],
attachedDatabase: database,
),
alias: null,
);
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get key =>
columnsByName['key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get partitionKey =>
columnsByName['partition_key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i1.DriftAny> get value =>
columnsByName['value']! as i1.GeneratedColumn<i1.DriftAny>;
}
i1.GeneratedColumn<String> _column_0(String aliasedName) =>
i1.GeneratedColumn<String>(
'key',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'PRIMARY KEY NOT NULL',
);
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>(
'partition_key',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<i1.DriftAny> _column_2(String aliasedName) =>
i1.GeneratedColumn<i1.DriftAny>(
'value',
aliasedName,
true,
type: i1.DriftSqlType.any,
$customConstraints: '',
);
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get origin =>
columnsByName['origin']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get iconData =>
columnsByName['icon_data']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get fetchDate =>
columnsByName['fetch_date']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>(
'origin',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'PRIMARY KEY NOT NULL',
);
i1.GeneratedColumn<i2.Uint8List> _column_4(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>(
'icon_data',
aliasedName,
false,
type: i1.DriftSqlType.blob,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<int> _column_5(String aliasedName) =>
i1.GeneratedColumn<int>(
'fetch_date',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL',
);
class Shape2 extends i0.VersionedTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get revision =>
columnsByName['revision']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get completionDate =>
columnsByName['completion_date']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_6(String aliasedName) =>
i1.GeneratedColumn<int>(
'revision',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<int> _column_7(String aliasedName) =>
i1.GeneratedColumn<int>(
'completion_date',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL',
);
class Shape3 extends i0.VersionedTable {
Shape3({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get key =>
columnsByName['key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get json =>
columnsByName['json']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get expireAt =>
columnsByName['expireAt']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get destroyKey =>
columnsByName['destroyKey']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_8(String aliasedName) =>
i1.GeneratedColumn<String>(
'json',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<int> _column_9(String aliasedName) =>
i1.GeneratedColumn<int>(
'expireAt',
aliasedName,
true,
type: i1.DriftSqlType.int,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_10(String aliasedName) =>
i1.GeneratedColumn<String>(
'destroyKey',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
final class Schema3 extends i0.VersionedSchema {
Schema3({required super.database}) : super(version: 3);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
setting,
iconCache,
onboarding,
riverpod,
toolbarButtonConfigs,
idxToolbarOrderKey,
];
late final Shape0 setting = Shape0(
source: i0.VersionedTable(
entityName: 'setting',
withoutRowId: false,
isStrict: true,
tableConstraints: [],
columns: [_column_0, _column_1, _column_2],
attachedDatabase: database,
),
alias: null,
);
late final Shape1 iconCache = Shape1(
source: i0.VersionedTable(
entityName: 'icon_cache',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_3, _column_4, _column_5],
attachedDatabase: database,
),
alias: null,
);
late final Shape2 onboarding = Shape2(
source: i0.VersionedTable(
entityName: 'onboarding',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_6, _column_7],
attachedDatabase: database,
),
alias: null,
);
late final Shape3 riverpod = Shape3(
source: i0.VersionedTable(
entityName: 'riverpod',
withoutRowId: true,
isStrict: false,
tableConstraints: [],
columns: [_column_0, _column_8, _column_9, _column_10],
attachedDatabase: database,
),
alias: null,
);
late final Shape4 toolbarButtonConfigs = Shape4(
source: i0.VersionedTable(
entityName: 'toolbar_button_configs',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_11, _column_12, _column_13, _column_14],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxToolbarOrderKey = i1.Index(
'idx_toolbar_order_key',
'CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs (order_key)',
);
}
class Shape4 extends i0.VersionedTable {
Shape4({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get buttonId =>
columnsByName['button_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get orderKey =>
columnsByName['order_key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get isVisible =>
columnsByName['is_visible']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get fallbackId =>
columnsByName['fallback_id']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_11(String aliasedName) =>
i1.GeneratedColumn<String>(
'button_id',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'NOT NULL PRIMARY KEY',
);
i1.GeneratedColumn<String> _column_12(String aliasedName) =>
i1.GeneratedColumn<String>(
'order_key',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<int> _column_13(String aliasedName) =>
i1.GeneratedColumn<int>(
'is_visible',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL DEFAULT TRUE',
defaultValue: const i1.CustomExpression('TRUE'),
);
i1.GeneratedColumn<String> _column_14(String aliasedName) =>
i1.GeneratedColumn<String>(
'fallback_id',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints:
'REFERENCES toolbar_button_configs(button_id)ON DELETE SET NULL',
);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = Schema2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
case 2:
final schema = Schema3(database: database);
final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema);
return 3;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
};
}
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(from1To2: from1To2, from2To3: from2To3),
);
@@ -0,0 +1,87 @@
CREATE TABLE setting (
"key" TEXT PRIMARY KEY NOT NULL,
partition_key TEXT,
"value" ANY
) STRICT;
CREATE TABLE icon_cache (
origin TEXT PRIMARY KEY NOT NULL,
icon_data BLOB NOT NULL,
fetch_date DATETIME NOT NULL
);
CREATE TABLE onboarding (
revision INTEGER NOT NULL,
completion_date DATETIME NOT NULL
);
CREATE TABLE riverpod (
"key" TEXT PRIMARY KEY NOT NULL,
json TEXT NOT NULL,
expireAt DATETIME,
destroyKey TEXT
) WITHOUT ROWID;
CREATE TABLE toolbar_button_configs (
button_id TEXT NOT NULL PRIMARY KEY,
order_key TEXT NOT NULL,
is_visible BOOLEAN NOT NULL DEFAULT TRUE,
fallback_id TEXT REFERENCES toolbar_button_configs(button_id) ON DELETE SET NULL
);
CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs(order_key);
toolbarLeadingOrderKey(:bucket AS INTEGER):
SELECT lexo_rank_previous(
:bucket,
(
SELECT order_key
FROM toolbar_button_configs
ORDER BY order_key
LIMIT 1
)
);
toolbarTrailingOrderKey(:bucket AS INTEGER):
SELECT lexo_rank_next(
:bucket,
(
SELECT order_key
FROM toolbar_button_configs
ORDER BY order_key DESC
LIMIT 1
)
);
toolbarOrderKeyAfterButton(:button_id AS TEXT):
WITH ordered_table AS (
SELECT
button_id,
order_key,
LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key
FROM toolbar_button_configs
)
SELECT lexo_rank_reorder_after(order_key, next_order_key)
FROM ordered_table
WHERE button_id = :button_id;
toolbarOrderKeyBeforeButton(:button_id AS TEXT):
WITH ordered_table AS (
SELECT
button_id,
order_key,
LAG(order_key) OVER (ORDER BY order_key) AS prev_order_key
FROM toolbar_button_configs
)
SELECT lexo_rank_reorder_before(order_key, prev_order_key)
FROM ordered_table
WHERE button_id = :button_id;
evictCacheEntries:
DELETE FROM icon_cache
WHERE rowid IN (
SELECT rowid
FROM icon_cache
ORDER BY fetch_date DESC
LIMIT -1 OFFSET :limit
);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/experimental/persist.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
final class RiverpodStorage extends Storage<String, String> {
final UserDatabase _db;
RiverpodStorage(this._db);
@override
Future<void> delete(String key) {
return _db.riverpod.deleteWhere((x) => x.key.equals(key));
}
@override
Future<void> deleteOutOfDate() {
return _db.riverpod.deleteWhere(
(x) => x.expireAt.isSmallerThanValue(DateTime.now()),
);
}
@override
Future<PersistedData<String>?> read(String key) async {
final query = _db.riverpod.select()..where((x) => x.key.equals(key));
final data = await query.getSingleOrNull();
return data.mapNotNull(
(data) => PersistedData(
data.json,
destroyKey: data.destroyKey,
expireAt: data.expireAt,
),
);
}
@override
Future<void> write(String key, String value, StorageOptions options) {
return _db.riverpod.insertOne(
RiverpodCompanion.insert(
key: key,
json: value,
destroyKey: Value(options.destroyKey),
expireAt: Value(
options.cacheTime.duration.mapNotNull(
(duration) => DateTime.now().add(duration),
),
),
),
mode: InsertMode.insertOrReplace,
);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'auth_settings.g.dart';
enum AutoLockMode { background, timeout }
@CopyWith()
@JsonSerializable()
class AuthSettings with FastEquatable {
final bool authenticationRequired;
final AutoLockMode autoLockMode;
final Duration timeout;
AuthSettings({
required this.authenticationRequired,
required this.autoLockMode,
required this.timeout,
});
AuthSettings.withDefaults({
bool? authenticationRequired,
AutoLockMode? autoLockMode,
Duration? timeout,
}) : this(
authenticationRequired: authenticationRequired ?? false,
autoLockMode: autoLockMode ?? AutoLockMode.background,
timeout: timeout ?? const Duration(minutes: 5),
);
AuthSettings withBackgroundLock() {
return copyWith(autoLockMode: AutoLockMode.background);
}
AuthSettings withTimeoutLock(Duration value) {
return copyWith(autoLockMode: AutoLockMode.timeout, timeout: value);
}
factory AuthSettings.fromJson(Map<String, dynamic> json) =>
_$AuthSettingsFromJson(json);
Map<String, dynamic> toJson() => _$AuthSettingsToJson(this);
@override
List<Object?> get hashParameters => [
authenticationRequired,
autoLockMode,
timeout,
];
}
@@ -0,0 +1,108 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_settings.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$AuthSettingsCWProxy {
AuthSettings authenticationRequired(bool authenticationRequired);
AuthSettings autoLockMode(AutoLockMode autoLockMode);
AuthSettings timeout(Duration timeout);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `AuthSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// AuthSettings(...).copyWith(id: 12, name: "My name")
/// ```
AuthSettings call({
bool authenticationRequired,
AutoLockMode autoLockMode,
Duration timeout,
});
}
/// Callable proxy for `copyWith` functionality.
/// Use as `instanceOfAuthSettings.copyWith(...)` or call `instanceOfAuthSettings.copyWith.fieldName(value)` for a single field.
class _$AuthSettingsCWProxyImpl implements _$AuthSettingsCWProxy {
const _$AuthSettingsCWProxyImpl(this._value);
final AuthSettings _value;
@override
AuthSettings authenticationRequired(bool authenticationRequired) =>
call(authenticationRequired: authenticationRequired);
@override
AuthSettings autoLockMode(AutoLockMode autoLockMode) =>
call(autoLockMode: autoLockMode);
@override
AuthSettings timeout(Duration timeout) => call(timeout: timeout);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `AuthSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// AuthSettings(...).copyWith(id: 12, name: "My name")
/// ```
AuthSettings call({
Object? authenticationRequired = const $CopyWithPlaceholder(),
Object? autoLockMode = const $CopyWithPlaceholder(),
Object? timeout = const $CopyWithPlaceholder(),
}) {
return AuthSettings(
authenticationRequired:
authenticationRequired == const $CopyWithPlaceholder() ||
authenticationRequired == null
? _value.authenticationRequired
// ignore: cast_nullable_to_non_nullable
: authenticationRequired as bool,
autoLockMode:
autoLockMode == const $CopyWithPlaceholder() || autoLockMode == null
? _value.autoLockMode
// ignore: cast_nullable_to_non_nullable
: autoLockMode as AutoLockMode,
timeout: timeout == const $CopyWithPlaceholder() || timeout == null
? _value.timeout
// ignore: cast_nullable_to_non_nullable
: timeout as Duration,
);
}
}
extension $AuthSettingsCopyWith on AuthSettings {
/// Returns a callable class used to build a new instance with modified fields.
/// Example: `instanceOfAuthSettings.copyWith(...)` or `instanceOfAuthSettings.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$AuthSettingsCWProxy get copyWith => _$AuthSettingsCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AuthSettings _$AuthSettingsFromJson(Map<String, dynamic> json) => AuthSettings(
authenticationRequired: json['authenticationRequired'] as bool,
autoLockMode: $enumDecode(_$AutoLockModeEnumMap, json['autoLockMode']),
timeout: Duration(microseconds: (json['timeout'] as num).toInt()),
);
Map<String, dynamic> _$AuthSettingsToJson(AuthSettings instance) =>
<String, dynamic>{
'authenticationRequired': instance.authenticationRequired,
'autoLockMode': _$AutoLockModeEnumMap[instance.autoLockMode]!,
'timeout': instance.timeout.inMicroseconds,
};
const _$AutoLockModeEnumMap = {
AutoLockMode.background: 'background',
AutoLockMode.timeout: 'timeout',
};
@@ -0,0 +1,390 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart';
part 'engine_settings.g.dart';
enum BuiltInDohProviders {
quad9('Quad9', 'https://dns.quad9.net/dns-query'),
mullvad('Mullvad', 'https://dns.mullvad.net/dns-query'),
adguard('AdGuard', 'https://dns.adguard-dns.com/dns-query'),
ffmuc('Freifunk München', 'https://doh.ffmuc.net/dns-query');
final String name;
final String url;
static bool isBuiltin(String url) =>
BuiltInDohProviders.values.any((provider) => provider.url == url);
const BuiltInDohProviders(this.name, this.url);
}
@CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
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!;
@override
bool get enterpriseRootsEnabled => super.enterpriseRootsEnabled!;
@override
List<String> get locales => super.locales!;
// Custom Tracking Protection overrides
@override
bool get blockCookies => super.blockCookies!;
@override
CustomCookiePolicy get customCookiePolicy => super.customCookiePolicy!;
@override
bool get blockTrackingContent => super.blockTrackingContent!;
@override
TrackingScope get trackingContentScope => super.trackingContentScope!;
@override
bool get blockCryptominers => super.blockCryptominers!;
@override
bool get blockFingerprinters => super.blockFingerprinters!;
@override
bool get blockRedirectTrackers => super.blockRedirectTrackers!;
@override
bool get blockSuspectedFingerprinters => super.blockSuspectedFingerprinters!;
@override
TrackingScope get suspectedFingerprintersScope =>
super.suspectedFingerprintersScope!;
@override
bool get allowListBaseline => super.allowListBaseline!;
@override
bool get allowListConvenience => super.allowListConvenience!;
// Web Content Settings
@override
bool get webFontsEnabled => super.webFontsEnabled!;
@override
bool get automaticFontSizeAdjustment => super.automaticFontSizeAdjustment!;
@override
double get fontSizeFactor => super.fontSizeFactor!;
@override
bool get fontInflationEnabled => super.fontInflationEnabled!;
@override
bool get inputAutoZoomEnabled => super.inputAutoZoomEnabled!;
// Process Isolation Settings (require app restart)
@override
bool get fissionEnabled => super.fissionEnabled!;
@override
bool get isolatedProcessEnabled => super.isolatedProcessEnabled!;
@override
bool get appZygoteProcessEnabled => super.appZygoteProcessEnabled!;
@override
bool get extensionsWebAPIEnabled => super.extensionsWebAPIEnabled!;
final QueryParameterStripping queryParameterStripping;
final BounceTrackingProtectionMode bounceTrackingProtectionMode;
@JsonKey(fromJson: _addonCollectionFromJson, toJson: _addonCollectionToJson)
final AddonCollection? addonCollection;
final DohSettingsMode dohSettingsMode;
final String dohProviderUrl;
final String dohDefaultProviderUrl;
final List<String> dohExceptionsList;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
DohSettings get dohSettings => DohSettings(
dohSettingsMode: dohSettingsMode,
dohProviderUrl: dohProviderUrl,
dohDefaultProviderUrl: dohDefaultProviderUrl,
dohExceptionsList: dohExceptionsList,
);
@override
@JsonKey(includeFromJson: false, includeToJson: false)
ContentBlocking get contentBlocking => ContentBlocking(
queryParameterStripping: queryParameterStripping,
queryParameterStrippingAllowList: '',
queryParameterStrippingStripList:
'__hsfp __hssc __hstc __s _bhlid _branch_match_id _branch_referrer _gl _hsenc _kx _openstat at_recipient_id at_recipient_list bbeml bsft_clkid bsft_uid dclid et_rid fb_action_ids fb_comment_id fbclid gbraid gclid guce_referrer guce_referrer_sig hsCtaTracking igshid irclickid mc_eid mkt_tok ml_subscriber ml_subscriber_hash msclkid mtm_cid oft_c oft_ck oft_d oft_id oft_ids oft_k oft_lk oft_sk oly_anon_id oly_enc_id pk_cid rb_clickid s_cid sc_customer sc_eh sc_uid sms_click sms_source sms_uph srsltid ss_email_id syclid ttclid twclid unicorn_click_id vero_conv vero_id vgo_ee wbraid wickedid yclid ymclid ysclid',
bounceTrackingProtectionMode: bounceTrackingProtectionMode,
);
final bool enablePdfJs;
EngineSettings({
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,
required super.userAgent,
required super.enterpriseRootsEnabled,
required this.queryParameterStripping,
required this.bounceTrackingProtectionMode,
required this.addonCollection,
required this.dohSettingsMode,
required this.dohProviderUrl,
required this.dohDefaultProviderUrl,
required this.dohExceptionsList,
required super.fingerprintingProtectionOverrides,
required this.enablePdfJs,
required super.locales,
required super.blockCookies,
required super.customCookiePolicy,
required super.blockTrackingContent,
required super.trackingContentScope,
required super.blockCryptominers,
required super.blockFingerprinters,
required super.blockRedirectTrackers,
required super.blockSuspectedFingerprinters,
required super.suspectedFingerprintersScope,
required super.allowListBaseline,
required super.allowListConvenience,
required super.webFontsEnabled,
required super.automaticFontSizeAdjustment,
required super.fontSizeFactor,
required super.fontInflationEnabled,
required super.displayDensityOverride,
required super.screenWidthOverride,
required super.screenHeightOverride,
required super.inputAutoZoomEnabled,
required super.fissionEnabled,
required super.isolatedProcessEnabled,
required super.appZygoteProcessEnabled,
required super.extensionsWebAPIEnabled,
required super.lnaBlocking,
required super.lnaBlockTrackers,
required super.lnaEnabled,
});
EngineSettings.withDefaults({
bool? javascriptEnabled,
TrackingProtectionPolicy? trackingProtectionPolicy,
HttpsOnlyMode? httpsOnlyMode,
bool? globalPrivacyControlEnabled,
ColorScheme? preferredColorScheme,
CookieBannerHandlingMode? cookieBannerHandlingMode,
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing,
bool? cookieBannerHandlingGlobalRules,
bool? cookieBannerHandlingGlobalRulesSubFrames,
WebContentIsolationStrategy? webContentIsolationStrategy,
QueryParameterStripping? queryParameterStripping,
BounceTrackingProtectionMode? bounceTrackingProtectionMode,
super.userAgent,
bool? enterpriseRootsEnabled,
this.addonCollection,
DohSettingsMode? dohSettingsMode,
String? dohProviderUrl,
String? dohDefaultProviderUrl,
List<String>? dohExceptionsList,
String? fingerprintingProtectionOverrides,
bool? enablePdfJs,
List<String>? locales,
bool? blockCookies,
CustomCookiePolicy? customCookiePolicy,
bool? blockTrackingContent,
TrackingScope? trackingContentScope,
bool? blockCryptominers,
bool? blockFingerprinters,
bool? blockRedirectTrackers,
bool? blockSuspectedFingerprinters,
TrackingScope? suspectedFingerprintersScope,
bool? allowListBaseline,
bool? allowListConvenience,
bool? webFontsEnabled,
bool? automaticFontSizeAdjustment,
double? fontSizeFactor,
bool? fontInflationEnabled,
super.displayDensityOverride,
super.screenWidthOverride,
super.screenHeightOverride,
bool? inputAutoZoomEnabled,
bool? fissionEnabled,
bool? isolatedProcessEnabled,
bool? appZygoteProcessEnabled,
bool? extensionsWebAPIEnabled,
super.lnaBlocking,
bool? lnaBlockTrackers,
bool? lnaEnabled,
}) : queryParameterStripping =
queryParameterStripping ?? QueryParameterStripping.enabled,
bounceTrackingProtectionMode =
bounceTrackingProtectionMode ?? BounceTrackingProtectionMode.enabled,
dohSettingsMode = dohSettingsMode ?? DohSettingsMode.increased,
dohProviderUrl = dohProviderUrl ?? BuiltInDohProviders.quad9.url,
dohDefaultProviderUrl =
dohDefaultProviderUrl ?? BuiltInDohProviders.quad9.url,
dohExceptionsList = dohExceptionsList ?? [],
enablePdfJs = enablePdfJs ?? true,
super(
javascriptEnabled: javascriptEnabled ?? true,
trackingProtectionPolicy:
trackingProtectionPolicy ?? TrackingProtectionPolicy.strict,
httpsOnlyMode: httpsOnlyMode ?? HttpsOnlyMode.enabled,
globalPrivacyControlEnabled: globalPrivacyControlEnabled ?? true,
preferredColorScheme: preferredColorScheme ?? ColorScheme.system,
cookieBannerHandlingMode:
cookieBannerHandlingMode ?? CookieBannerHandlingMode.rejectAll,
cookieBannerHandlingModePrivateBrowsing:
cookieBannerHandlingModePrivateBrowsing ??
CookieBannerHandlingMode.rejectAll,
cookieBannerHandlingGlobalRules:
cookieBannerHandlingGlobalRules ?? true,
cookieBannerHandlingGlobalRulesSubFrames:
cookieBannerHandlingGlobalRulesSubFrames ?? true,
webContentIsolationStrategy:
webContentIsolationStrategy ??
WebContentIsolationStrategy.isolateHighValue,
enterpriseRootsEnabled: enterpriseRootsEnabled ?? false,
fingerprintingProtectionOverrides:
fingerprintingProtectionOverrides ??
FingerprintOverrides.defaults().toString(),
locales:
locales ??
WidgetsBinding.instance.platformDispatcher.locales
.map((x) => x.toLanguageTag())
.toList(),
blockCookies: blockCookies ?? true,
customCookiePolicy:
customCookiePolicy ?? CustomCookiePolicy.totalProtection,
blockTrackingContent: blockTrackingContent ?? true,
trackingContentScope: trackingContentScope ?? TrackingScope.all,
blockCryptominers: blockCryptominers ?? true,
blockFingerprinters: blockFingerprinters ?? true,
blockRedirectTrackers: blockRedirectTrackers ?? true,
blockSuspectedFingerprinters: blockSuspectedFingerprinters ?? true,
suspectedFingerprintersScope:
suspectedFingerprintersScope ?? TrackingScope.all,
allowListBaseline: allowListBaseline ?? true,
allowListConvenience: allowListConvenience ?? false,
webFontsEnabled: webFontsEnabled ?? true,
automaticFontSizeAdjustment: automaticFontSizeAdjustment ?? true,
fontSizeFactor: fontSizeFactor ?? 1.0,
fontInflationEnabled: fontInflationEnabled ?? false,
inputAutoZoomEnabled: inputAutoZoomEnabled ?? true,
fissionEnabled: fissionEnabled ?? true,
isolatedProcessEnabled: isolatedProcessEnabled ?? false,
appZygoteProcessEnabled: appZygoteProcessEnabled ?? false,
extensionsWebAPIEnabled: extensionsWebAPIEnabled ?? true,
lnaBlockTrackers: lnaBlockTrackers ?? true,
lnaEnabled: lnaEnabled ?? true,
);
static AddonCollection? _addonCollectionFromJson(String? json) =>
json.mapNotNull(
(collection) => AddonCollection.decode(jsonDecode(collection) as List),
);
static String? _addonCollectionToJson(AddonCollection? collection) =>
collection.mapNotNull((collection) => jsonEncode(collection.encode()));
factory EngineSettings.fromJson(Map<String, dynamic> json) =>
_$EngineSettingsFromJson(json);
Map<String, dynamic> toJson() => _$EngineSettingsToJson(this);
@override
List<Object?> get hashParameters => [
javascriptEnabled,
trackingProtectionPolicy,
httpsOnlyMode,
globalPrivacyControlEnabled,
preferredColorScheme,
cookieBannerHandlingMode,
cookieBannerHandlingModePrivateBrowsing,
cookieBannerHandlingGlobalRules,
cookieBannerHandlingGlobalRulesSubFrames,
webContentIsolationStrategy,
userAgent,
enterpriseRootsEnabled,
queryParameterStripping,
bounceTrackingProtectionMode,
addonCollection,
dohSettingsMode,
dohProviderUrl,
dohDefaultProviderUrl,
dohExceptionsList,
fingerprintingProtectionOverrides,
enablePdfJs,
locales,
blockCookies,
customCookiePolicy,
blockTrackingContent,
trackingContentScope,
blockCryptominers,
blockFingerprinters,
blockRedirectTrackers,
blockSuspectedFingerprinters,
suspectedFingerprintersScope,
allowListBaseline,
allowListConvenience,
webFontsEnabled,
automaticFontSizeAdjustment,
fontSizeFactor,
fontInflationEnabled,
displayDensityOverride,
screenWidthOverride,
screenHeightOverride,
inputAutoZoomEnabled,
fissionEnabled,
isolatedProcessEnabled,
appZygoteProcessEnabled,
extensionsWebAPIEnabled,
lnaBlocking,
lnaBlockTrackers,
lnaEnabled,
];
}
@@ -0,0 +1,934 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'engine_settings.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$EngineSettingsCWProxy {
EngineSettings javascriptEnabled(bool? javascriptEnabled);
EngineSettings trackingProtectionPolicy(
TrackingProtectionPolicy? trackingProtectionPolicy,
);
EngineSettings httpsOnlyMode(HttpsOnlyMode? httpsOnlyMode);
EngineSettings globalPrivacyControlEnabled(bool? globalPrivacyControlEnabled);
EngineSettings preferredColorScheme(ColorScheme? preferredColorScheme);
EngineSettings cookieBannerHandlingMode(
CookieBannerHandlingMode? cookieBannerHandlingMode,
);
EngineSettings cookieBannerHandlingModePrivateBrowsing(
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing,
);
EngineSettings cookieBannerHandlingGlobalRules(
bool? cookieBannerHandlingGlobalRules,
);
EngineSettings cookieBannerHandlingGlobalRulesSubFrames(
bool? cookieBannerHandlingGlobalRulesSubFrames,
);
EngineSettings webContentIsolationStrategy(
WebContentIsolationStrategy? webContentIsolationStrategy,
);
EngineSettings userAgent(String? userAgent);
EngineSettings enterpriseRootsEnabled(bool? enterpriseRootsEnabled);
EngineSettings queryParameterStripping(
QueryParameterStripping queryParameterStripping,
);
EngineSettings bounceTrackingProtectionMode(
BounceTrackingProtectionMode bounceTrackingProtectionMode,
);
EngineSettings addonCollection(AddonCollection? addonCollection);
EngineSettings dohSettingsMode(DohSettingsMode dohSettingsMode);
EngineSettings dohProviderUrl(String dohProviderUrl);
EngineSettings dohDefaultProviderUrl(String dohDefaultProviderUrl);
EngineSettings dohExceptionsList(List<String> dohExceptionsList);
EngineSettings fingerprintingProtectionOverrides(
String? fingerprintingProtectionOverrides,
);
EngineSettings enablePdfJs(bool enablePdfJs);
EngineSettings locales(List<String>? locales);
EngineSettings blockCookies(bool? blockCookies);
EngineSettings customCookiePolicy(CustomCookiePolicy? customCookiePolicy);
EngineSettings blockTrackingContent(bool? blockTrackingContent);
EngineSettings trackingContentScope(TrackingScope? trackingContentScope);
EngineSettings blockCryptominers(bool? blockCryptominers);
EngineSettings blockFingerprinters(bool? blockFingerprinters);
EngineSettings blockRedirectTrackers(bool? blockRedirectTrackers);
EngineSettings blockSuspectedFingerprinters(
bool? blockSuspectedFingerprinters,
);
EngineSettings suspectedFingerprintersScope(
TrackingScope? suspectedFingerprintersScope,
);
EngineSettings allowListBaseline(bool? allowListBaseline);
EngineSettings allowListConvenience(bool? allowListConvenience);
EngineSettings webFontsEnabled(bool? webFontsEnabled);
EngineSettings automaticFontSizeAdjustment(bool? automaticFontSizeAdjustment);
EngineSettings fontSizeFactor(double? fontSizeFactor);
EngineSettings fontInflationEnabled(bool? fontInflationEnabled);
EngineSettings displayDensityOverride(double? displayDensityOverride);
EngineSettings screenWidthOverride(int? screenWidthOverride);
EngineSettings screenHeightOverride(int? screenHeightOverride);
EngineSettings inputAutoZoomEnabled(bool? inputAutoZoomEnabled);
EngineSettings fissionEnabled(bool? fissionEnabled);
EngineSettings isolatedProcessEnabled(bool? isolatedProcessEnabled);
EngineSettings appZygoteProcessEnabled(bool? appZygoteProcessEnabled);
EngineSettings extensionsWebAPIEnabled(bool? extensionsWebAPIEnabled);
EngineSettings lnaBlocking(bool? lnaBlocking);
EngineSettings lnaBlockTrackers(bool? lnaBlockTrackers);
EngineSettings lnaEnabled(bool? lnaEnabled);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// EngineSettings(...).copyWith(id: 12, name: "My name")
/// ```
EngineSettings call({
bool? javascriptEnabled,
TrackingProtectionPolicy? trackingProtectionPolicy,
HttpsOnlyMode? httpsOnlyMode,
bool? globalPrivacyControlEnabled,
ColorScheme? preferredColorScheme,
CookieBannerHandlingMode? cookieBannerHandlingMode,
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing,
bool? cookieBannerHandlingGlobalRules,
bool? cookieBannerHandlingGlobalRulesSubFrames,
WebContentIsolationStrategy? webContentIsolationStrategy,
String? userAgent,
bool? enterpriseRootsEnabled,
QueryParameterStripping queryParameterStripping,
BounceTrackingProtectionMode bounceTrackingProtectionMode,
AddonCollection? addonCollection,
DohSettingsMode dohSettingsMode,
String dohProviderUrl,
String dohDefaultProviderUrl,
List<String> dohExceptionsList,
String? fingerprintingProtectionOverrides,
bool enablePdfJs,
List<String>? locales,
bool? blockCookies,
CustomCookiePolicy? customCookiePolicy,
bool? blockTrackingContent,
TrackingScope? trackingContentScope,
bool? blockCryptominers,
bool? blockFingerprinters,
bool? blockRedirectTrackers,
bool? blockSuspectedFingerprinters,
TrackingScope? suspectedFingerprintersScope,
bool? allowListBaseline,
bool? allowListConvenience,
bool? webFontsEnabled,
bool? automaticFontSizeAdjustment,
double? fontSizeFactor,
bool? fontInflationEnabled,
double? displayDensityOverride,
int? screenWidthOverride,
int? screenHeightOverride,
bool? inputAutoZoomEnabled,
bool? fissionEnabled,
bool? isolatedProcessEnabled,
bool? appZygoteProcessEnabled,
bool? extensionsWebAPIEnabled,
bool? lnaBlocking,
bool? lnaBlockTrackers,
bool? lnaEnabled,
});
}
/// Callable proxy for `copyWith` functionality.
/// Use as `instanceOfEngineSettings.copyWith(...)` or call `instanceOfEngineSettings.copyWith.fieldName(value)` for a single field.
class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
const _$EngineSettingsCWProxyImpl(this._value);
final EngineSettings _value;
@override
EngineSettings javascriptEnabled(bool? javascriptEnabled) =>
call(javascriptEnabled: javascriptEnabled);
@override
EngineSettings trackingProtectionPolicy(
TrackingProtectionPolicy? trackingProtectionPolicy,
) => call(trackingProtectionPolicy: trackingProtectionPolicy);
@override
EngineSettings httpsOnlyMode(HttpsOnlyMode? httpsOnlyMode) =>
call(httpsOnlyMode: httpsOnlyMode);
@override
EngineSettings globalPrivacyControlEnabled(
bool? globalPrivacyControlEnabled,
) => call(globalPrivacyControlEnabled: globalPrivacyControlEnabled);
@override
EngineSettings preferredColorScheme(ColorScheme? preferredColorScheme) =>
call(preferredColorScheme: preferredColorScheme);
@override
EngineSettings cookieBannerHandlingMode(
CookieBannerHandlingMode? cookieBannerHandlingMode,
) => call(cookieBannerHandlingMode: cookieBannerHandlingMode);
@override
EngineSettings cookieBannerHandlingModePrivateBrowsing(
CookieBannerHandlingMode? cookieBannerHandlingModePrivateBrowsing,
) => call(
cookieBannerHandlingModePrivateBrowsing:
cookieBannerHandlingModePrivateBrowsing,
);
@override
EngineSettings cookieBannerHandlingGlobalRules(
bool? cookieBannerHandlingGlobalRules,
) => call(cookieBannerHandlingGlobalRules: cookieBannerHandlingGlobalRules);
@override
EngineSettings cookieBannerHandlingGlobalRulesSubFrames(
bool? cookieBannerHandlingGlobalRulesSubFrames,
) => call(
cookieBannerHandlingGlobalRulesSubFrames:
cookieBannerHandlingGlobalRulesSubFrames,
);
@override
EngineSettings webContentIsolationStrategy(
WebContentIsolationStrategy? webContentIsolationStrategy,
) => call(webContentIsolationStrategy: webContentIsolationStrategy);
@override
EngineSettings userAgent(String? userAgent) => call(userAgent: userAgent);
@override
EngineSettings enterpriseRootsEnabled(bool? enterpriseRootsEnabled) =>
call(enterpriseRootsEnabled: enterpriseRootsEnabled);
@override
EngineSettings queryParameterStripping(
QueryParameterStripping queryParameterStripping,
) => call(queryParameterStripping: queryParameterStripping);
@override
EngineSettings bounceTrackingProtectionMode(
BounceTrackingProtectionMode bounceTrackingProtectionMode,
) => call(bounceTrackingProtectionMode: bounceTrackingProtectionMode);
@override
EngineSettings addonCollection(AddonCollection? addonCollection) =>
call(addonCollection: addonCollection);
@override
EngineSettings dohSettingsMode(DohSettingsMode dohSettingsMode) =>
call(dohSettingsMode: dohSettingsMode);
@override
EngineSettings dohProviderUrl(String dohProviderUrl) =>
call(dohProviderUrl: dohProviderUrl);
@override
EngineSettings dohDefaultProviderUrl(String dohDefaultProviderUrl) =>
call(dohDefaultProviderUrl: dohDefaultProviderUrl);
@override
EngineSettings dohExceptionsList(List<String> dohExceptionsList) =>
call(dohExceptionsList: dohExceptionsList);
@override
EngineSettings fingerprintingProtectionOverrides(
String? fingerprintingProtectionOverrides,
) => call(
fingerprintingProtectionOverrides: fingerprintingProtectionOverrides,
);
@override
EngineSettings enablePdfJs(bool enablePdfJs) =>
call(enablePdfJs: enablePdfJs);
@override
EngineSettings locales(List<String>? locales) => call(locales: locales);
@override
EngineSettings blockCookies(bool? blockCookies) =>
call(blockCookies: blockCookies);
@override
EngineSettings customCookiePolicy(CustomCookiePolicy? customCookiePolicy) =>
call(customCookiePolicy: customCookiePolicy);
@override
EngineSettings blockTrackingContent(bool? blockTrackingContent) =>
call(blockTrackingContent: blockTrackingContent);
@override
EngineSettings trackingContentScope(TrackingScope? trackingContentScope) =>
call(trackingContentScope: trackingContentScope);
@override
EngineSettings blockCryptominers(bool? blockCryptominers) =>
call(blockCryptominers: blockCryptominers);
@override
EngineSettings blockFingerprinters(bool? blockFingerprinters) =>
call(blockFingerprinters: blockFingerprinters);
@override
EngineSettings blockRedirectTrackers(bool? blockRedirectTrackers) =>
call(blockRedirectTrackers: blockRedirectTrackers);
@override
EngineSettings blockSuspectedFingerprinters(
bool? blockSuspectedFingerprinters,
) => call(blockSuspectedFingerprinters: blockSuspectedFingerprinters);
@override
EngineSettings suspectedFingerprintersScope(
TrackingScope? suspectedFingerprintersScope,
) => call(suspectedFingerprintersScope: suspectedFingerprintersScope);
@override
EngineSettings allowListBaseline(bool? allowListBaseline) =>
call(allowListBaseline: allowListBaseline);
@override
EngineSettings allowListConvenience(bool? allowListConvenience) =>
call(allowListConvenience: allowListConvenience);
@override
EngineSettings webFontsEnabled(bool? webFontsEnabled) =>
call(webFontsEnabled: webFontsEnabled);
@override
EngineSettings automaticFontSizeAdjustment(
bool? automaticFontSizeAdjustment,
) => call(automaticFontSizeAdjustment: automaticFontSizeAdjustment);
@override
EngineSettings fontSizeFactor(double? fontSizeFactor) =>
call(fontSizeFactor: fontSizeFactor);
@override
EngineSettings fontInflationEnabled(bool? fontInflationEnabled) =>
call(fontInflationEnabled: fontInflationEnabled);
@override
EngineSettings displayDensityOverride(double? displayDensityOverride) =>
call(displayDensityOverride: displayDensityOverride);
@override
EngineSettings screenWidthOverride(int? screenWidthOverride) =>
call(screenWidthOverride: screenWidthOverride);
@override
EngineSettings screenHeightOverride(int? screenHeightOverride) =>
call(screenHeightOverride: screenHeightOverride);
@override
EngineSettings inputAutoZoomEnabled(bool? inputAutoZoomEnabled) =>
call(inputAutoZoomEnabled: inputAutoZoomEnabled);
@override
EngineSettings fissionEnabled(bool? fissionEnabled) =>
call(fissionEnabled: fissionEnabled);
@override
EngineSettings isolatedProcessEnabled(bool? isolatedProcessEnabled) =>
call(isolatedProcessEnabled: isolatedProcessEnabled);
@override
EngineSettings appZygoteProcessEnabled(bool? appZygoteProcessEnabled) =>
call(appZygoteProcessEnabled: appZygoteProcessEnabled);
@override
EngineSettings extensionsWebAPIEnabled(bool? extensionsWebAPIEnabled) =>
call(extensionsWebAPIEnabled: extensionsWebAPIEnabled);
@override
EngineSettings lnaBlocking(bool? lnaBlocking) =>
call(lnaBlocking: lnaBlocking);
@override
EngineSettings lnaBlockTrackers(bool? lnaBlockTrackers) =>
call(lnaBlockTrackers: lnaBlockTrackers);
@override
EngineSettings lnaEnabled(bool? lnaEnabled) => call(lnaEnabled: lnaEnabled);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// EngineSettings(...).copyWith(id: 12, name: "My name")
/// ```
EngineSettings call({
Object? javascriptEnabled = const $CopyWithPlaceholder(),
Object? trackingProtectionPolicy = const $CopyWithPlaceholder(),
Object? httpsOnlyMode = const $CopyWithPlaceholder(),
Object? globalPrivacyControlEnabled = const $CopyWithPlaceholder(),
Object? preferredColorScheme = const $CopyWithPlaceholder(),
Object? cookieBannerHandlingMode = const $CopyWithPlaceholder(),
Object? cookieBannerHandlingModePrivateBrowsing =
const $CopyWithPlaceholder(),
Object? cookieBannerHandlingGlobalRules = const $CopyWithPlaceholder(),
Object? cookieBannerHandlingGlobalRulesSubFrames =
const $CopyWithPlaceholder(),
Object? webContentIsolationStrategy = const $CopyWithPlaceholder(),
Object? userAgent = const $CopyWithPlaceholder(),
Object? enterpriseRootsEnabled = const $CopyWithPlaceholder(),
Object? queryParameterStripping = const $CopyWithPlaceholder(),
Object? bounceTrackingProtectionMode = const $CopyWithPlaceholder(),
Object? addonCollection = const $CopyWithPlaceholder(),
Object? dohSettingsMode = const $CopyWithPlaceholder(),
Object? dohProviderUrl = const $CopyWithPlaceholder(),
Object? dohDefaultProviderUrl = const $CopyWithPlaceholder(),
Object? dohExceptionsList = const $CopyWithPlaceholder(),
Object? fingerprintingProtectionOverrides = const $CopyWithPlaceholder(),
Object? enablePdfJs = const $CopyWithPlaceholder(),
Object? locales = const $CopyWithPlaceholder(),
Object? blockCookies = const $CopyWithPlaceholder(),
Object? customCookiePolicy = const $CopyWithPlaceholder(),
Object? blockTrackingContent = const $CopyWithPlaceholder(),
Object? trackingContentScope = const $CopyWithPlaceholder(),
Object? blockCryptominers = const $CopyWithPlaceholder(),
Object? blockFingerprinters = const $CopyWithPlaceholder(),
Object? blockRedirectTrackers = const $CopyWithPlaceholder(),
Object? blockSuspectedFingerprinters = const $CopyWithPlaceholder(),
Object? suspectedFingerprintersScope = const $CopyWithPlaceholder(),
Object? allowListBaseline = const $CopyWithPlaceholder(),
Object? allowListConvenience = const $CopyWithPlaceholder(),
Object? webFontsEnabled = const $CopyWithPlaceholder(),
Object? automaticFontSizeAdjustment = const $CopyWithPlaceholder(),
Object? fontSizeFactor = const $CopyWithPlaceholder(),
Object? fontInflationEnabled = const $CopyWithPlaceholder(),
Object? displayDensityOverride = const $CopyWithPlaceholder(),
Object? screenWidthOverride = const $CopyWithPlaceholder(),
Object? screenHeightOverride = const $CopyWithPlaceholder(),
Object? inputAutoZoomEnabled = const $CopyWithPlaceholder(),
Object? fissionEnabled = const $CopyWithPlaceholder(),
Object? isolatedProcessEnabled = const $CopyWithPlaceholder(),
Object? appZygoteProcessEnabled = const $CopyWithPlaceholder(),
Object? extensionsWebAPIEnabled = const $CopyWithPlaceholder(),
Object? lnaBlocking = const $CopyWithPlaceholder(),
Object? lnaBlockTrackers = const $CopyWithPlaceholder(),
Object? lnaEnabled = const $CopyWithPlaceholder(),
}) {
return EngineSettings(
javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder()
? _value.javascriptEnabled
// ignore: cast_nullable_to_non_nullable
: javascriptEnabled as bool?,
trackingProtectionPolicy:
trackingProtectionPolicy == const $CopyWithPlaceholder()
? _value.trackingProtectionPolicy
// ignore: cast_nullable_to_non_nullable
: trackingProtectionPolicy as TrackingProtectionPolicy?,
httpsOnlyMode: httpsOnlyMode == const $CopyWithPlaceholder()
? _value.httpsOnlyMode
// ignore: cast_nullable_to_non_nullable
: httpsOnlyMode as HttpsOnlyMode?,
globalPrivacyControlEnabled:
globalPrivacyControlEnabled == const $CopyWithPlaceholder()
? _value.globalPrivacyControlEnabled
// ignore: cast_nullable_to_non_nullable
: globalPrivacyControlEnabled as bool?,
preferredColorScheme: preferredColorScheme == const $CopyWithPlaceholder()
? _value.preferredColorScheme
// ignore: cast_nullable_to_non_nullable
: preferredColorScheme as ColorScheme?,
cookieBannerHandlingMode:
cookieBannerHandlingMode == const $CopyWithPlaceholder()
? _value.cookieBannerHandlingMode
// ignore: cast_nullable_to_non_nullable
: cookieBannerHandlingMode as CookieBannerHandlingMode?,
cookieBannerHandlingModePrivateBrowsing:
cookieBannerHandlingModePrivateBrowsing ==
const $CopyWithPlaceholder()
? _value.cookieBannerHandlingModePrivateBrowsing
// ignore: cast_nullable_to_non_nullable
: cookieBannerHandlingModePrivateBrowsing
as CookieBannerHandlingMode?,
cookieBannerHandlingGlobalRules:
cookieBannerHandlingGlobalRules == const $CopyWithPlaceholder()
? _value.cookieBannerHandlingGlobalRules
// ignore: cast_nullable_to_non_nullable
: cookieBannerHandlingGlobalRules as bool?,
cookieBannerHandlingGlobalRulesSubFrames:
cookieBannerHandlingGlobalRulesSubFrames ==
const $CopyWithPlaceholder()
? _value.cookieBannerHandlingGlobalRulesSubFrames
// ignore: cast_nullable_to_non_nullable
: cookieBannerHandlingGlobalRulesSubFrames as bool?,
webContentIsolationStrategy:
webContentIsolationStrategy == const $CopyWithPlaceholder()
? _value.webContentIsolationStrategy
// ignore: cast_nullable_to_non_nullable
: webContentIsolationStrategy as WebContentIsolationStrategy?,
userAgent: userAgent == const $CopyWithPlaceholder()
? _value.userAgent
// ignore: cast_nullable_to_non_nullable
: userAgent as String?,
enterpriseRootsEnabled:
enterpriseRootsEnabled == const $CopyWithPlaceholder()
? _value.enterpriseRootsEnabled
// ignore: cast_nullable_to_non_nullable
: enterpriseRootsEnabled as bool?,
queryParameterStripping:
queryParameterStripping == const $CopyWithPlaceholder() ||
queryParameterStripping == null
? _value.queryParameterStripping
// ignore: cast_nullable_to_non_nullable
: queryParameterStripping as QueryParameterStripping,
bounceTrackingProtectionMode:
bounceTrackingProtectionMode == const $CopyWithPlaceholder() ||
bounceTrackingProtectionMode == null
? _value.bounceTrackingProtectionMode
// ignore: cast_nullable_to_non_nullable
: bounceTrackingProtectionMode as BounceTrackingProtectionMode,
addonCollection: addonCollection == const $CopyWithPlaceholder()
? _value.addonCollection
// ignore: cast_nullable_to_non_nullable
: addonCollection as AddonCollection?,
dohSettingsMode:
dohSettingsMode == const $CopyWithPlaceholder() ||
dohSettingsMode == null
? _value.dohSettingsMode
// ignore: cast_nullable_to_non_nullable
: dohSettingsMode as DohSettingsMode,
dohProviderUrl:
dohProviderUrl == const $CopyWithPlaceholder() ||
dohProviderUrl == null
? _value.dohProviderUrl
// ignore: cast_nullable_to_non_nullable
: dohProviderUrl as String,
dohDefaultProviderUrl:
dohDefaultProviderUrl == const $CopyWithPlaceholder() ||
dohDefaultProviderUrl == null
? _value.dohDefaultProviderUrl
// ignore: cast_nullable_to_non_nullable
: dohDefaultProviderUrl as String,
dohExceptionsList:
dohExceptionsList == const $CopyWithPlaceholder() ||
dohExceptionsList == null
? _value.dohExceptionsList
// ignore: cast_nullable_to_non_nullable
: dohExceptionsList as List<String>,
fingerprintingProtectionOverrides:
fingerprintingProtectionOverrides == const $CopyWithPlaceholder()
? _value.fingerprintingProtectionOverrides
// ignore: cast_nullable_to_non_nullable
: fingerprintingProtectionOverrides as String?,
enablePdfJs:
enablePdfJs == const $CopyWithPlaceholder() || enablePdfJs == null
? _value.enablePdfJs
// ignore: cast_nullable_to_non_nullable
: enablePdfJs as bool,
locales: locales == const $CopyWithPlaceholder()
? _value.locales
// ignore: cast_nullable_to_non_nullable
: locales as List<String>?,
blockCookies: blockCookies == const $CopyWithPlaceholder()
? _value.blockCookies
// ignore: cast_nullable_to_non_nullable
: blockCookies as bool?,
customCookiePolicy: customCookiePolicy == const $CopyWithPlaceholder()
? _value.customCookiePolicy
// ignore: cast_nullable_to_non_nullable
: customCookiePolicy as CustomCookiePolicy?,
blockTrackingContent: blockTrackingContent == const $CopyWithPlaceholder()
? _value.blockTrackingContent
// ignore: cast_nullable_to_non_nullable
: blockTrackingContent as bool?,
trackingContentScope: trackingContentScope == const $CopyWithPlaceholder()
? _value.trackingContentScope
// ignore: cast_nullable_to_non_nullable
: trackingContentScope as TrackingScope?,
blockCryptominers: blockCryptominers == const $CopyWithPlaceholder()
? _value.blockCryptominers
// ignore: cast_nullable_to_non_nullable
: blockCryptominers as bool?,
blockFingerprinters: blockFingerprinters == const $CopyWithPlaceholder()
? _value.blockFingerprinters
// ignore: cast_nullable_to_non_nullable
: blockFingerprinters as bool?,
blockRedirectTrackers:
blockRedirectTrackers == const $CopyWithPlaceholder()
? _value.blockRedirectTrackers
// ignore: cast_nullable_to_non_nullable
: blockRedirectTrackers as bool?,
blockSuspectedFingerprinters:
blockSuspectedFingerprinters == const $CopyWithPlaceholder()
? _value.blockSuspectedFingerprinters
// ignore: cast_nullable_to_non_nullable
: blockSuspectedFingerprinters as bool?,
suspectedFingerprintersScope:
suspectedFingerprintersScope == const $CopyWithPlaceholder()
? _value.suspectedFingerprintersScope
// ignore: cast_nullable_to_non_nullable
: suspectedFingerprintersScope as TrackingScope?,
allowListBaseline: allowListBaseline == const $CopyWithPlaceholder()
? _value.allowListBaseline
// ignore: cast_nullable_to_non_nullable
: allowListBaseline as bool?,
allowListConvenience: allowListConvenience == const $CopyWithPlaceholder()
? _value.allowListConvenience
// ignore: cast_nullable_to_non_nullable
: allowListConvenience as bool?,
webFontsEnabled: webFontsEnabled == const $CopyWithPlaceholder()
? _value.webFontsEnabled
// ignore: cast_nullable_to_non_nullable
: webFontsEnabled as bool?,
automaticFontSizeAdjustment:
automaticFontSizeAdjustment == const $CopyWithPlaceholder()
? _value.automaticFontSizeAdjustment
// ignore: cast_nullable_to_non_nullable
: automaticFontSizeAdjustment as bool?,
fontSizeFactor: fontSizeFactor == const $CopyWithPlaceholder()
? _value.fontSizeFactor
// ignore: cast_nullable_to_non_nullable
: fontSizeFactor as double?,
fontInflationEnabled: fontInflationEnabled == const $CopyWithPlaceholder()
? _value.fontInflationEnabled
// ignore: cast_nullable_to_non_nullable
: fontInflationEnabled as bool?,
displayDensityOverride:
displayDensityOverride == const $CopyWithPlaceholder()
? _value.displayDensityOverride
// ignore: cast_nullable_to_non_nullable
: displayDensityOverride as double?,
screenWidthOverride: screenWidthOverride == const $CopyWithPlaceholder()
? _value.screenWidthOverride
// ignore: cast_nullable_to_non_nullable
: screenWidthOverride as int?,
screenHeightOverride: screenHeightOverride == const $CopyWithPlaceholder()
? _value.screenHeightOverride
// ignore: cast_nullable_to_non_nullable
: screenHeightOverride as int?,
inputAutoZoomEnabled: inputAutoZoomEnabled == const $CopyWithPlaceholder()
? _value.inputAutoZoomEnabled
// ignore: cast_nullable_to_non_nullable
: inputAutoZoomEnabled as bool?,
fissionEnabled: fissionEnabled == const $CopyWithPlaceholder()
? _value.fissionEnabled
// ignore: cast_nullable_to_non_nullable
: fissionEnabled as bool?,
isolatedProcessEnabled:
isolatedProcessEnabled == const $CopyWithPlaceholder()
? _value.isolatedProcessEnabled
// ignore: cast_nullable_to_non_nullable
: isolatedProcessEnabled as bool?,
appZygoteProcessEnabled:
appZygoteProcessEnabled == const $CopyWithPlaceholder()
? _value.appZygoteProcessEnabled
// ignore: cast_nullable_to_non_nullable
: appZygoteProcessEnabled as bool?,
extensionsWebAPIEnabled:
extensionsWebAPIEnabled == const $CopyWithPlaceholder()
? _value.extensionsWebAPIEnabled
// ignore: cast_nullable_to_non_nullable
: extensionsWebAPIEnabled as bool?,
lnaBlocking: lnaBlocking == const $CopyWithPlaceholder()
? _value.lnaBlocking
// ignore: cast_nullable_to_non_nullable
: lnaBlocking as bool?,
lnaBlockTrackers: lnaBlockTrackers == const $CopyWithPlaceholder()
? _value.lnaBlockTrackers
// ignore: cast_nullable_to_non_nullable
: lnaBlockTrackers as bool?,
lnaEnabled: lnaEnabled == const $CopyWithPlaceholder()
? _value.lnaEnabled
// ignore: cast_nullable_to_non_nullable
: lnaEnabled as bool?,
);
}
}
extension $EngineSettingsCopyWith on EngineSettings {
/// Returns a callable class used to build a new instance with modified fields.
/// Example: `instanceOfEngineSettings.copyWith(...)` or `instanceOfEngineSettings.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$EngineSettingsCWProxy get copyWith => _$EngineSettingsCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
EngineSettings _$EngineSettingsFromJson(
Map<String, dynamic> json,
) => EngineSettings.withDefaults(
javascriptEnabled: json['javascriptEnabled'] as bool?,
trackingProtectionPolicy: $enumDecodeNullable(
_$TrackingProtectionPolicyEnumMap,
json['trackingProtectionPolicy'],
),
httpsOnlyMode: $enumDecodeNullable(
_$HttpsOnlyModeEnumMap,
json['httpsOnlyMode'],
),
globalPrivacyControlEnabled: json['globalPrivacyControlEnabled'] as bool?,
preferredColorScheme: $enumDecodeNullable(
_$ColorSchemeEnumMap,
json['preferredColorScheme'],
),
cookieBannerHandlingMode: $enumDecodeNullable(
_$CookieBannerHandlingModeEnumMap,
json['cookieBannerHandlingMode'],
),
cookieBannerHandlingModePrivateBrowsing: $enumDecodeNullable(
_$CookieBannerHandlingModeEnumMap,
json['cookieBannerHandlingModePrivateBrowsing'],
),
cookieBannerHandlingGlobalRules:
json['cookieBannerHandlingGlobalRules'] as bool?,
cookieBannerHandlingGlobalRulesSubFrames:
json['cookieBannerHandlingGlobalRulesSubFrames'] as bool?,
webContentIsolationStrategy: $enumDecodeNullable(
_$WebContentIsolationStrategyEnumMap,
json['webContentIsolationStrategy'],
),
queryParameterStripping: $enumDecodeNullable(
_$QueryParameterStrippingEnumMap,
json['queryParameterStripping'],
),
bounceTrackingProtectionMode: $enumDecodeNullable(
_$BounceTrackingProtectionModeEnumMap,
json['bounceTrackingProtectionMode'],
),
userAgent: json['userAgent'] as String?,
enterpriseRootsEnabled: json['enterpriseRootsEnabled'] as bool?,
addonCollection: EngineSettings._addonCollectionFromJson(
json['addonCollection'] as String?,
),
dohSettingsMode: $enumDecodeNullable(
_$DohSettingsModeEnumMap,
json['dohSettingsMode'],
),
dohProviderUrl: json['dohProviderUrl'] as String?,
dohDefaultProviderUrl: json['dohDefaultProviderUrl'] as String?,
dohExceptionsList: (json['dohExceptionsList'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
fingerprintingProtectionOverrides:
json['fingerprintingProtectionOverrides'] as String?,
enablePdfJs: json['enablePdfJs'] as bool?,
locales: (json['locales'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
blockCookies: json['blockCookies'] as bool?,
customCookiePolicy: $enumDecodeNullable(
_$CustomCookiePolicyEnumMap,
json['customCookiePolicy'],
),
blockTrackingContent: json['blockTrackingContent'] as bool?,
trackingContentScope: $enumDecodeNullable(
_$TrackingScopeEnumMap,
json['trackingContentScope'],
),
blockCryptominers: json['blockCryptominers'] as bool?,
blockFingerprinters: json['blockFingerprinters'] as bool?,
blockRedirectTrackers: json['blockRedirectTrackers'] as bool?,
blockSuspectedFingerprinters: json['blockSuspectedFingerprinters'] as bool?,
suspectedFingerprintersScope: $enumDecodeNullable(
_$TrackingScopeEnumMap,
json['suspectedFingerprintersScope'],
),
allowListBaseline: json['allowListBaseline'] as bool?,
allowListConvenience: json['allowListConvenience'] as bool?,
webFontsEnabled: json['webFontsEnabled'] as bool?,
automaticFontSizeAdjustment: json['automaticFontSizeAdjustment'] as bool?,
fontSizeFactor: (json['fontSizeFactor'] as num?)?.toDouble(),
fontInflationEnabled: json['fontInflationEnabled'] as bool?,
displayDensityOverride: (json['displayDensityOverride'] as num?)?.toDouble(),
screenWidthOverride: (json['screenWidthOverride'] as num?)?.toInt(),
screenHeightOverride: (json['screenHeightOverride'] as num?)?.toInt(),
inputAutoZoomEnabled: json['inputAutoZoomEnabled'] as bool?,
fissionEnabled: json['fissionEnabled'] as bool?,
isolatedProcessEnabled: json['isolatedProcessEnabled'] as bool?,
appZygoteProcessEnabled: json['appZygoteProcessEnabled'] as bool?,
extensionsWebAPIEnabled: json['extensionsWebAPIEnabled'] as bool?,
lnaBlocking: json['lnaBlocking'] as bool?,
lnaBlockTrackers: json['lnaBlockTrackers'] as bool?,
lnaEnabled: json['lnaEnabled'] as bool?,
);
Map<String, dynamic> _$EngineSettingsToJson(
EngineSettings instance,
) => <String, dynamic>{
'userAgent': instance.userAgent,
'fingerprintingProtectionOverrides':
instance.fingerprintingProtectionOverrides,
'displayDensityOverride': instance.displayDensityOverride,
'screenWidthOverride': instance.screenWidthOverride,
'screenHeightOverride': instance.screenHeightOverride,
'lnaBlocking': instance.lnaBlocking,
'lnaBlockTrackers': instance.lnaBlockTrackers,
'lnaEnabled': instance.lnaEnabled,
'javascriptEnabled': instance.javascriptEnabled,
'trackingProtectionPolicy':
_$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy]!,
'httpsOnlyMode': _$HttpsOnlyModeEnumMap[instance.httpsOnlyMode]!,
'preferredColorScheme': _$ColorSchemeEnumMap[instance.preferredColorScheme]!,
'globalPrivacyControlEnabled': instance.globalPrivacyControlEnabled,
'cookieBannerHandlingMode':
_$CookieBannerHandlingModeEnumMap[instance.cookieBannerHandlingMode]!,
'cookieBannerHandlingModePrivateBrowsing':
_$CookieBannerHandlingModeEnumMap[instance
.cookieBannerHandlingModePrivateBrowsing]!,
'cookieBannerHandlingGlobalRules': instance.cookieBannerHandlingGlobalRules,
'cookieBannerHandlingGlobalRulesSubFrames':
instance.cookieBannerHandlingGlobalRulesSubFrames,
'webContentIsolationStrategy':
_$WebContentIsolationStrategyEnumMap[instance
.webContentIsolationStrategy]!,
'enterpriseRootsEnabled': instance.enterpriseRootsEnabled,
'locales': instance.locales,
'blockCookies': instance.blockCookies,
'customCookiePolicy':
_$CustomCookiePolicyEnumMap[instance.customCookiePolicy]!,
'blockTrackingContent': instance.blockTrackingContent,
'trackingContentScope':
_$TrackingScopeEnumMap[instance.trackingContentScope]!,
'blockCryptominers': instance.blockCryptominers,
'blockFingerprinters': instance.blockFingerprinters,
'blockRedirectTrackers': instance.blockRedirectTrackers,
'blockSuspectedFingerprinters': instance.blockSuspectedFingerprinters,
'suspectedFingerprintersScope':
_$TrackingScopeEnumMap[instance.suspectedFingerprintersScope]!,
'allowListBaseline': instance.allowListBaseline,
'allowListConvenience': instance.allowListConvenience,
'webFontsEnabled': instance.webFontsEnabled,
'automaticFontSizeAdjustment': instance.automaticFontSizeAdjustment,
'fontSizeFactor': instance.fontSizeFactor,
'fontInflationEnabled': instance.fontInflationEnabled,
'inputAutoZoomEnabled': instance.inputAutoZoomEnabled,
'fissionEnabled': instance.fissionEnabled,
'isolatedProcessEnabled': instance.isolatedProcessEnabled,
'appZygoteProcessEnabled': instance.appZygoteProcessEnabled,
'extensionsWebAPIEnabled': instance.extensionsWebAPIEnabled,
'queryParameterStripping':
_$QueryParameterStrippingEnumMap[instance.queryParameterStripping]!,
'bounceTrackingProtectionMode':
_$BounceTrackingProtectionModeEnumMap[instance
.bounceTrackingProtectionMode]!,
'addonCollection': EngineSettings._addonCollectionToJson(
instance.addonCollection,
),
'dohSettingsMode': _$DohSettingsModeEnumMap[instance.dohSettingsMode]!,
'dohProviderUrl': instance.dohProviderUrl,
'dohDefaultProviderUrl': instance.dohDefaultProviderUrl,
'dohExceptionsList': instance.dohExceptionsList,
'enablePdfJs': instance.enablePdfJs,
};
const _$TrackingProtectionPolicyEnumMap = {
TrackingProtectionPolicy.none: 'none',
TrackingProtectionPolicy.recommended: 'recommended',
TrackingProtectionPolicy.strict: 'strict',
TrackingProtectionPolicy.custom: 'custom',
};
const _$HttpsOnlyModeEnumMap = {
HttpsOnlyMode.disabled: 'disabled',
HttpsOnlyMode.privateOnly: 'privateOnly',
HttpsOnlyMode.enabled: 'enabled',
};
const _$ColorSchemeEnumMap = {
ColorScheme.system: 'system',
ColorScheme.light: 'light',
ColorScheme.dark: 'dark',
};
const _$CookieBannerHandlingModeEnumMap = {
CookieBannerHandlingMode.disabled: 'disabled',
CookieBannerHandlingMode.rejectAll: 'rejectAll',
CookieBannerHandlingMode.rejectOrAcceptAll: 'rejectOrAcceptAll',
};
const _$WebContentIsolationStrategyEnumMap = {
WebContentIsolationStrategy.isolateNothing: 'isolateNothing',
WebContentIsolationStrategy.isolateEverything: 'isolateEverything',
WebContentIsolationStrategy.isolateHighValue: 'isolateHighValue',
};
const _$QueryParameterStrippingEnumMap = {
QueryParameterStripping.disabled: 'disabled',
QueryParameterStripping.privateOnly: 'privateOnly',
QueryParameterStripping.enabled: 'enabled',
};
const _$BounceTrackingProtectionModeEnumMap = {
BounceTrackingProtectionMode.disabled: 'disabled',
BounceTrackingProtectionMode.enabled: 'enabled',
BounceTrackingProtectionMode.enabledStandby: 'enabledStandby',
BounceTrackingProtectionMode.enabledDryRun: 'enabledDryRun',
};
const _$DohSettingsModeEnumMap = {
DohSettingsMode.geckoDefault: 'geckoDefault',
DohSettingsMode.increased: 'increased',
DohSettingsMode.max: 'max',
DohSettingsMode.off: 'off',
};
const _$CustomCookiePolicyEnumMap = {
CustomCookiePolicy.totalProtection: 'totalProtection',
CustomCookiePolicy.crossSiteTrackers: 'crossSiteTrackers',
CustomCookiePolicy.unvisited: 'unvisited',
CustomCookiePolicy.thirdParty: 'thirdParty',
CustomCookiePolicy.allCookies: 'allCookies',
};
const _$TrackingScopeEnumMap = {
TrackingScope.all: 'all',
TrackingScope.privateOnly: 'privateOnly',
};
@@ -0,0 +1,357 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package: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';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/bangs/data/models/bang_group.dart';
import 'package:weblibre/features/bangs/data/models/bang_key.dart';
import 'package:weblibre/features/search/domain/entities/abstract/i_search_suggestion_provider.dart';
part 'general_settings.g.dart';
const _fallbackSearchProvider = BangKey(
group: BangGroup.general,
trigger: 'wikipedia',
);
const _fallbackAutocompleteProvider = SearchSuggestionProviders.none;
const defaultUiScaleFactor = 1.0;
const minUiScaleFactor = 0.5;
const maxUiScaleFactor = 1.5;
const uiScaleFactorStep = 0.05;
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
enum TabIntentOpenSetting { regular, private, ask }
enum NewTabPosition { first, end }
enum TabBarPosition { top, bottom }
enum TabBarLayout { withTitle, compact }
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 double uiScaleFactor;
final bool disableAnimations;
final bool showModalBarrier;
final bool enableReadability;
final bool enforceReadability;
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
@BangKeyConverter()
final BangKey? defaultSearchProvider;
final SearchSuggestionProviders defaultSearchSuggestionsProvider;
final bool createChildTabsOption;
final bool enableLocalAiFeatures;
final bool showContainerUi;
final bool showIsolatedTabUi;
@JsonKey(name: 'defaultCreateTabType')
final TabType storedDefaultCreateTabType;
final NewTabPosition newTabPosition;
final TabIntentOpenSetting tabIntentOpenSetting;
final bool autoHideTabBar;
final TabBarSwipeAction tabBarSwipeAction;
final Duration historyAutoCleanInterval;
final bool tabViewBottomSheet;
final bool tabBarShowContextualBar;
final bool tabBarShowQuickTabSwitcherBar;
final TabBarPosition tabBarPosition;
final TabBarLayout tabBarLayout;
final QuickTabSwitcherMode quickTabSwitcherMode;
final bool pullToRefreshEnabled;
final bool useExternalDownloadManager;
final bool doubleBackCloseTab;
final Duration unassignedTabsAutoCleanInterval;
final int maxSearchHistoryEntries;
final bool allowClipboardAccess;
final bool tabListShowFavicons;
final bool quickTabSwitcherShowTitles;
final bool quickTabSwitcherShowHistorySuggestions;
final String syncServerOverride;
final String syncTokenServerOverride;
final bool urlCleanerEnabled;
final bool urlCleanerAutoApply;
final bool urlCleanerAllowReferralMarketing;
final String urlCleanerCatalogUrl;
final String urlCleanerHashUrl;
final bool urlCleanerAutoUpdate;
final int? urlCleanerLastCheckEpochMs;
final bool urlCleanerLastUpdateWasAuto;
final TabType smallWebTabType;
final bool tabBarLongPressUrlCopy;
final bool unshortenerEnabled;
final String unshortenerToken;
final bool allowNonManifestPwaInstall;
GeneralSettings({
required this.themeMode,
required this.uiScaleFactor,
required this.disableAnimations,
required this.showModalBarrier,
required this.enableReadability,
required this.enforceReadability,
required this.deleteBrowsingDataOnQuit,
required this.defaultSearchProvider,
required this.defaultSearchSuggestionsProvider,
required this.createChildTabsOption,
required this.enableLocalAiFeatures,
required this.showContainerUi,
required this.showIsolatedTabUi,
required this.storedDefaultCreateTabType,
required this.newTabPosition,
required this.tabIntentOpenSetting,
required this.autoHideTabBar,
required this.tabBarSwipeAction,
required this.historyAutoCleanInterval,
required this.tabViewBottomSheet,
required this.tabBarShowContextualBar,
required this.tabBarShowQuickTabSwitcherBar,
required this.tabBarPosition,
required this.tabBarLayout,
required this.quickTabSwitcherMode,
required this.pullToRefreshEnabled,
required this.useExternalDownloadManager,
required this.doubleBackCloseTab,
required this.unassignedTabsAutoCleanInterval,
required this.maxSearchHistoryEntries,
required this.allowClipboardAccess,
required this.tabListShowFavicons,
required this.quickTabSwitcherShowTitles,
required this.quickTabSwitcherShowHistorySuggestions,
required this.syncServerOverride,
required this.syncTokenServerOverride,
required this.urlCleanerEnabled,
required this.urlCleanerAutoApply,
required this.urlCleanerAllowReferralMarketing,
required this.urlCleanerCatalogUrl,
required this.urlCleanerHashUrl,
required this.urlCleanerAutoUpdate,
required this.urlCleanerLastCheckEpochMs,
required this.urlCleanerLastUpdateWasAuto,
required this.smallWebTabType,
required this.tabBarLongPressUrlCopy,
required this.unshortenerEnabled,
required this.unshortenerToken,
required this.allowNonManifestPwaInstall,
});
GeneralSettings.withDefaults({
ThemeMode? themeMode,
double? uiScaleFactor,
bool? disableAnimations,
bool? showModalBarrier,
bool? enableReadability,
bool? enforceReadability,
this.deleteBrowsingDataOnQuit,
BangKey? defaultSearchProvider,
SearchSuggestionProviders? defaultSearchSuggestionsProvider,
bool? createChildTabsOption,
bool? enableLocalAiFeatures,
bool? showContainerUi,
bool? showIsolatedTabUi,
TabType? storedDefaultCreateTabType,
NewTabPosition? newTabPosition,
TabIntentOpenSetting? tabIntentOpenSetting,
bool? autoHideTabBar,
TabBarSwipeAction? tabBarSwipeAction,
Duration? historyAutoCleanInterval,
bool? tabViewBottomSheet,
bool? tabBarShowContextualBar,
bool? tabBarShowQuickTabSwitcherBar,
TabBarPosition? tabBarPosition,
TabBarLayout? tabBarLayout,
QuickTabSwitcherMode? quickTabSwitcherMode,
bool? pullToRefreshEnabled,
bool? useExternalDownloadManager,
bool? doubleBackCloseTab,
Duration? unassignedTabsAutoCleanInterval,
int? maxSearchHistoryEntries,
bool? allowClipboardAccess,
bool? tabListShowFavicons,
bool? quickTabSwitcherShowTitles,
bool? quickTabSwitcherShowHistorySuggestions,
String? syncServerOverride,
String? syncTokenServerOverride,
bool? urlCleanerEnabled,
bool? urlCleanerAutoApply,
bool? urlCleanerAllowReferralMarketing,
String? urlCleanerCatalogUrl,
String? urlCleanerHashUrl,
bool? urlCleanerAutoUpdate,
this.urlCleanerLastCheckEpochMs,
bool? urlCleanerLastUpdateWasAuto,
TabType? smallWebTabType,
bool? tabBarLongPressUrlCopy,
bool? unshortenerEnabled,
String? unshortenerToken,
bool? allowNonManifestPwaInstall,
}) : themeMode = themeMode ?? ThemeMode.dark,
uiScaleFactor = uiScaleFactor ?? defaultUiScaleFactor,
disableAnimations = disableAnimations ?? false,
showModalBarrier = showModalBarrier ?? true,
enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false,
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
defaultSearchSuggestionsProvider =
defaultSearchSuggestionsProvider ?? _fallbackAutocompleteProvider,
createChildTabsOption = createChildTabsOption ?? false,
enableLocalAiFeatures = enableLocalAiFeatures ?? true,
showContainerUi = showContainerUi ?? true,
showIsolatedTabUi = showIsolatedTabUi ?? true,
storedDefaultCreateTabType =
storedDefaultCreateTabType ?? TabType.regular,
newTabPosition = newTabPosition ?? NewTabPosition.first,
tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask,
autoHideTabBar = autoHideTabBar ?? true,
tabBarSwipeAction =
tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened,
historyAutoCleanInterval =
historyAutoCleanInterval ?? const Duration(days: 90),
tabViewBottomSheet = tabViewBottomSheet ?? false,
tabBarShowContextualBar = tabBarShowContextualBar ?? true,
tabBarShowQuickTabSwitcherBar = tabBarShowQuickTabSwitcherBar ?? true,
tabBarPosition = tabBarPosition ?? TabBarPosition.bottom,
tabBarLayout = tabBarLayout ?? TabBarLayout.compact,
quickTabSwitcherMode =
quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs,
pullToRefreshEnabled = pullToRefreshEnabled ?? true,
useExternalDownloadManager = useExternalDownloadManager ?? false,
doubleBackCloseTab = doubleBackCloseTab ?? true,
unassignedTabsAutoCleanInterval =
unassignedTabsAutoCleanInterval ?? Duration.zero,
maxSearchHistoryEntries = maxSearchHistoryEntries ?? 5,
allowClipboardAccess = allowClipboardAccess ?? true,
tabListShowFavicons = tabListShowFavicons ?? false,
quickTabSwitcherShowTitles = quickTabSwitcherShowTitles ?? true,
quickTabSwitcherShowHistorySuggestions =
quickTabSwitcherShowHistorySuggestions ?? true,
syncServerOverride = syncServerOverride ?? '',
syncTokenServerOverride = syncTokenServerOverride ?? '',
urlCleanerEnabled = urlCleanerEnabled ?? true,
urlCleanerAutoApply = urlCleanerAutoApply ?? false,
urlCleanerAllowReferralMarketing =
urlCleanerAllowReferralMarketing ?? false,
urlCleanerCatalogUrl =
urlCleanerCatalogUrl ??
'https://rules2.clearurls.xyz/data.minify.json',
urlCleanerHashUrl =
urlCleanerHashUrl ??
'https://rules2.clearurls.xyz/rules.minify.hash',
urlCleanerAutoUpdate = urlCleanerAutoUpdate ?? false,
urlCleanerLastUpdateWasAuto = urlCleanerLastUpdateWasAuto ?? false,
smallWebTabType = smallWebTabType ?? TabType.private,
tabBarLongPressUrlCopy = tabBarLongPressUrlCopy ?? true,
unshortenerEnabled = unshortenerEnabled ?? false,
unshortenerToken = unshortenerToken ?? '',
allowNonManifestPwaInstall = allowNonManifestPwaInstall ?? false;
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
_$GeneralSettingsFromJson(json);
Map<String, dynamic> toJson() => _$GeneralSettingsToJson(this);
TabType get effectiveDefaultCreateTabType {
if (!showIsolatedTabUi && storedDefaultCreateTabType == TabType.isolated) {
return TabType.regular;
}
return storedDefaultCreateTabType;
}
QuickTabSwitcherMode effectiveUiQuickTabSwitcherMode() {
if (!showContainerUi &&
quickTabSwitcherMode == QuickTabSwitcherMode.containerTabs) {
return QuickTabSwitcherMode.lastUsedTabs;
}
return quickTabSwitcherMode;
}
@override
List<Object?> get hashParameters => [
themeMode,
uiScaleFactor,
disableAnimations,
showModalBarrier,
enableReadability,
enforceReadability,
deleteBrowsingDataOnQuit,
defaultSearchProvider,
defaultSearchSuggestionsProvider,
createChildTabsOption,
enableLocalAiFeatures,
showContainerUi,
showIsolatedTabUi,
storedDefaultCreateTabType,
newTabPosition,
tabIntentOpenSetting,
autoHideTabBar,
tabBarSwipeAction,
historyAutoCleanInterval,
tabViewBottomSheet,
tabBarShowContextualBar,
tabBarShowQuickTabSwitcherBar,
tabBarPosition,
tabBarLayout,
quickTabSwitcherMode,
pullToRefreshEnabled,
useExternalDownloadManager,
doubleBackCloseTab,
unassignedTabsAutoCleanInterval,
maxSearchHistoryEntries,
allowClipboardAccess,
tabListShowFavicons,
quickTabSwitcherShowTitles,
quickTabSwitcherShowHistorySuggestions,
syncServerOverride,
syncTokenServerOverride,
urlCleanerEnabled,
urlCleanerAutoApply,
urlCleanerAllowReferralMarketing,
urlCleanerCatalogUrl,
urlCleanerHashUrl,
urlCleanerAutoUpdate,
urlCleanerLastCheckEpochMs,
urlCleanerLastUpdateWasAuto,
smallWebTabType,
tabBarLongPressUrlCopy,
unshortenerEnabled,
unshortenerToken,
allowNonManifestPwaInstall,
];
}
@@ -0,0 +1,987 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'general_settings.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$GeneralSettingsCWProxy {
GeneralSettings themeMode(ThemeMode themeMode);
GeneralSettings uiScaleFactor(double uiScaleFactor);
GeneralSettings disableAnimations(bool disableAnimations);
GeneralSettings showModalBarrier(bool showModalBarrier);
GeneralSettings enableReadability(bool enableReadability);
GeneralSettings enforceReadability(bool enforceReadability);
GeneralSettings deleteBrowsingDataOnQuit(
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
);
GeneralSettings defaultSearchProvider(BangKey? defaultSearchProvider);
GeneralSettings defaultSearchSuggestionsProvider(
SearchSuggestionProviders defaultSearchSuggestionsProvider,
);
GeneralSettings createChildTabsOption(bool createChildTabsOption);
GeneralSettings enableLocalAiFeatures(bool enableLocalAiFeatures);
GeneralSettings showContainerUi(bool showContainerUi);
GeneralSettings showIsolatedTabUi(bool showIsolatedTabUi);
GeneralSettings storedDefaultCreateTabType(
TabType storedDefaultCreateTabType,
);
GeneralSettings newTabPosition(NewTabPosition newTabPosition);
GeneralSettings tabIntentOpenSetting(
TabIntentOpenSetting tabIntentOpenSetting,
);
GeneralSettings autoHideTabBar(bool autoHideTabBar);
GeneralSettings tabBarSwipeAction(TabBarSwipeAction tabBarSwipeAction);
GeneralSettings historyAutoCleanInterval(Duration historyAutoCleanInterval);
GeneralSettings tabViewBottomSheet(bool tabViewBottomSheet);
GeneralSettings tabBarShowContextualBar(bool tabBarShowContextualBar);
GeneralSettings tabBarShowQuickTabSwitcherBar(
bool tabBarShowQuickTabSwitcherBar,
);
GeneralSettings tabBarPosition(TabBarPosition tabBarPosition);
GeneralSettings tabBarLayout(TabBarLayout tabBarLayout);
GeneralSettings quickTabSwitcherMode(
QuickTabSwitcherMode quickTabSwitcherMode,
);
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled);
GeneralSettings useExternalDownloadManager(bool useExternalDownloadManager);
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab);
GeneralSettings unassignedTabsAutoCleanInterval(
Duration unassignedTabsAutoCleanInterval,
);
GeneralSettings maxSearchHistoryEntries(int maxSearchHistoryEntries);
GeneralSettings allowClipboardAccess(bool allowClipboardAccess);
GeneralSettings tabListShowFavicons(bool tabListShowFavicons);
GeneralSettings quickTabSwitcherShowTitles(bool quickTabSwitcherShowTitles);
GeneralSettings quickTabSwitcherShowHistorySuggestions(
bool quickTabSwitcherShowHistorySuggestions,
);
GeneralSettings syncServerOverride(String syncServerOverride);
GeneralSettings syncTokenServerOverride(String syncTokenServerOverride);
GeneralSettings urlCleanerEnabled(bool urlCleanerEnabled);
GeneralSettings urlCleanerAutoApply(bool urlCleanerAutoApply);
GeneralSettings urlCleanerAllowReferralMarketing(
bool urlCleanerAllowReferralMarketing,
);
GeneralSettings urlCleanerCatalogUrl(String urlCleanerCatalogUrl);
GeneralSettings urlCleanerHashUrl(String urlCleanerHashUrl);
GeneralSettings urlCleanerAutoUpdate(bool urlCleanerAutoUpdate);
GeneralSettings urlCleanerLastCheckEpochMs(int? urlCleanerLastCheckEpochMs);
GeneralSettings urlCleanerLastUpdateWasAuto(bool urlCleanerLastUpdateWasAuto);
GeneralSettings smallWebTabType(TabType smallWebTabType);
GeneralSettings tabBarLongPressUrlCopy(bool tabBarLongPressUrlCopy);
GeneralSettings unshortenerEnabled(bool unshortenerEnabled);
GeneralSettings unshortenerToken(String unshortenerToken);
GeneralSettings allowNonManifestPwaInstall(bool allowNonManifestPwaInstall);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// GeneralSettings(...).copyWith(id: 12, name: "My name")
/// ```
GeneralSettings call({
ThemeMode themeMode,
double uiScaleFactor,
bool disableAnimations,
bool showModalBarrier,
bool enableReadability,
bool enforceReadability,
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
BangKey? defaultSearchProvider,
SearchSuggestionProviders defaultSearchSuggestionsProvider,
bool createChildTabsOption,
bool enableLocalAiFeatures,
bool showContainerUi,
bool showIsolatedTabUi,
TabType storedDefaultCreateTabType,
NewTabPosition newTabPosition,
TabIntentOpenSetting tabIntentOpenSetting,
bool autoHideTabBar,
TabBarSwipeAction tabBarSwipeAction,
Duration historyAutoCleanInterval,
bool tabViewBottomSheet,
bool tabBarShowContextualBar,
bool tabBarShowQuickTabSwitcherBar,
TabBarPosition tabBarPosition,
TabBarLayout tabBarLayout,
QuickTabSwitcherMode quickTabSwitcherMode,
bool pullToRefreshEnabled,
bool useExternalDownloadManager,
bool doubleBackCloseTab,
Duration unassignedTabsAutoCleanInterval,
int maxSearchHistoryEntries,
bool allowClipboardAccess,
bool tabListShowFavicons,
bool quickTabSwitcherShowTitles,
bool quickTabSwitcherShowHistorySuggestions,
String syncServerOverride,
String syncTokenServerOverride,
bool urlCleanerEnabled,
bool urlCleanerAutoApply,
bool urlCleanerAllowReferralMarketing,
String urlCleanerCatalogUrl,
String urlCleanerHashUrl,
bool urlCleanerAutoUpdate,
int? urlCleanerLastCheckEpochMs,
bool urlCleanerLastUpdateWasAuto,
TabType smallWebTabType,
bool tabBarLongPressUrlCopy,
bool unshortenerEnabled,
String unshortenerToken,
bool allowNonManifestPwaInstall,
});
}
/// Callable proxy for `copyWith` functionality.
/// Use as `instanceOfGeneralSettings.copyWith(...)` or call `instanceOfGeneralSettings.copyWith.fieldName(value)` for a single field.
class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
const _$GeneralSettingsCWProxyImpl(this._value);
final GeneralSettings _value;
@override
GeneralSettings themeMode(ThemeMode themeMode) => call(themeMode: themeMode);
@override
GeneralSettings uiScaleFactor(double uiScaleFactor) =>
call(uiScaleFactor: uiScaleFactor);
@override
GeneralSettings disableAnimations(bool disableAnimations) =>
call(disableAnimations: disableAnimations);
@override
GeneralSettings showModalBarrier(bool showModalBarrier) =>
call(showModalBarrier: showModalBarrier);
@override
GeneralSettings enableReadability(bool enableReadability) =>
call(enableReadability: enableReadability);
@override
GeneralSettings enforceReadability(bool enforceReadability) =>
call(enforceReadability: enforceReadability);
@override
GeneralSettings deleteBrowsingDataOnQuit(
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
) => call(deleteBrowsingDataOnQuit: deleteBrowsingDataOnQuit);
@override
GeneralSettings defaultSearchProvider(BangKey? defaultSearchProvider) =>
call(defaultSearchProvider: defaultSearchProvider);
@override
GeneralSettings defaultSearchSuggestionsProvider(
SearchSuggestionProviders defaultSearchSuggestionsProvider,
) => call(defaultSearchSuggestionsProvider: defaultSearchSuggestionsProvider);
@override
GeneralSettings createChildTabsOption(bool createChildTabsOption) =>
call(createChildTabsOption: createChildTabsOption);
@override
GeneralSettings enableLocalAiFeatures(bool enableLocalAiFeatures) =>
call(enableLocalAiFeatures: enableLocalAiFeatures);
@override
GeneralSettings showContainerUi(bool showContainerUi) =>
call(showContainerUi: showContainerUi);
@override
GeneralSettings showIsolatedTabUi(bool showIsolatedTabUi) =>
call(showIsolatedTabUi: showIsolatedTabUi);
@override
GeneralSettings storedDefaultCreateTabType(
TabType storedDefaultCreateTabType,
) => call(storedDefaultCreateTabType: storedDefaultCreateTabType);
@override
GeneralSettings newTabPosition(NewTabPosition newTabPosition) =>
call(newTabPosition: newTabPosition);
@override
GeneralSettings tabIntentOpenSetting(
TabIntentOpenSetting tabIntentOpenSetting,
) => call(tabIntentOpenSetting: tabIntentOpenSetting);
@override
GeneralSettings autoHideTabBar(bool autoHideTabBar) =>
call(autoHideTabBar: autoHideTabBar);
@override
GeneralSettings tabBarSwipeAction(TabBarSwipeAction tabBarSwipeAction) =>
call(tabBarSwipeAction: tabBarSwipeAction);
@override
GeneralSettings historyAutoCleanInterval(Duration historyAutoCleanInterval) =>
call(historyAutoCleanInterval: historyAutoCleanInterval);
@override
GeneralSettings tabViewBottomSheet(bool tabViewBottomSheet) =>
call(tabViewBottomSheet: tabViewBottomSheet);
@override
GeneralSettings tabBarShowContextualBar(bool tabBarShowContextualBar) =>
call(tabBarShowContextualBar: tabBarShowContextualBar);
@override
GeneralSettings tabBarShowQuickTabSwitcherBar(
bool tabBarShowQuickTabSwitcherBar,
) => call(tabBarShowQuickTabSwitcherBar: tabBarShowQuickTabSwitcherBar);
@override
GeneralSettings tabBarPosition(TabBarPosition tabBarPosition) =>
call(tabBarPosition: tabBarPosition);
@override
GeneralSettings tabBarLayout(TabBarLayout tabBarLayout) =>
call(tabBarLayout: tabBarLayout);
@override
GeneralSettings quickTabSwitcherMode(
QuickTabSwitcherMode quickTabSwitcherMode,
) => call(quickTabSwitcherMode: quickTabSwitcherMode);
@override
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) =>
call(pullToRefreshEnabled: pullToRefreshEnabled);
@override
GeneralSettings useExternalDownloadManager(bool useExternalDownloadManager) =>
call(useExternalDownloadManager: useExternalDownloadManager);
@override
GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab) =>
call(doubleBackCloseTab: doubleBackCloseTab);
@override
GeneralSettings unassignedTabsAutoCleanInterval(
Duration unassignedTabsAutoCleanInterval,
) => call(unassignedTabsAutoCleanInterval: unassignedTabsAutoCleanInterval);
@override
GeneralSettings maxSearchHistoryEntries(int maxSearchHistoryEntries) =>
call(maxSearchHistoryEntries: maxSearchHistoryEntries);
@override
GeneralSettings allowClipboardAccess(bool allowClipboardAccess) =>
call(allowClipboardAccess: allowClipboardAccess);
@override
GeneralSettings tabListShowFavicons(bool tabListShowFavicons) =>
call(tabListShowFavicons: tabListShowFavicons);
@override
GeneralSettings quickTabSwitcherShowTitles(bool quickTabSwitcherShowTitles) =>
call(quickTabSwitcherShowTitles: quickTabSwitcherShowTitles);
@override
GeneralSettings quickTabSwitcherShowHistorySuggestions(
bool quickTabSwitcherShowHistorySuggestions,
) => call(
quickTabSwitcherShowHistorySuggestions:
quickTabSwitcherShowHistorySuggestions,
);
@override
GeneralSettings syncServerOverride(String syncServerOverride) =>
call(syncServerOverride: syncServerOverride);
@override
GeneralSettings syncTokenServerOverride(String syncTokenServerOverride) =>
call(syncTokenServerOverride: syncTokenServerOverride);
@override
GeneralSettings urlCleanerEnabled(bool urlCleanerEnabled) =>
call(urlCleanerEnabled: urlCleanerEnabled);
@override
GeneralSettings urlCleanerAutoApply(bool urlCleanerAutoApply) =>
call(urlCleanerAutoApply: urlCleanerAutoApply);
@override
GeneralSettings urlCleanerAllowReferralMarketing(
bool urlCleanerAllowReferralMarketing,
) => call(urlCleanerAllowReferralMarketing: urlCleanerAllowReferralMarketing);
@override
GeneralSettings urlCleanerCatalogUrl(String urlCleanerCatalogUrl) =>
call(urlCleanerCatalogUrl: urlCleanerCatalogUrl);
@override
GeneralSettings urlCleanerHashUrl(String urlCleanerHashUrl) =>
call(urlCleanerHashUrl: urlCleanerHashUrl);
@override
GeneralSettings urlCleanerAutoUpdate(bool urlCleanerAutoUpdate) =>
call(urlCleanerAutoUpdate: urlCleanerAutoUpdate);
@override
GeneralSettings urlCleanerLastCheckEpochMs(int? urlCleanerLastCheckEpochMs) =>
call(urlCleanerLastCheckEpochMs: urlCleanerLastCheckEpochMs);
@override
GeneralSettings urlCleanerLastUpdateWasAuto(
bool urlCleanerLastUpdateWasAuto,
) => call(urlCleanerLastUpdateWasAuto: urlCleanerLastUpdateWasAuto);
@override
GeneralSettings smallWebTabType(TabType smallWebTabType) =>
call(smallWebTabType: smallWebTabType);
@override
GeneralSettings tabBarLongPressUrlCopy(bool tabBarLongPressUrlCopy) =>
call(tabBarLongPressUrlCopy: tabBarLongPressUrlCopy);
@override
GeneralSettings unshortenerEnabled(bool unshortenerEnabled) =>
call(unshortenerEnabled: unshortenerEnabled);
@override
GeneralSettings unshortenerToken(String unshortenerToken) =>
call(unshortenerToken: unshortenerToken);
@override
GeneralSettings allowNonManifestPwaInstall(bool allowNonManifestPwaInstall) =>
call(allowNonManifestPwaInstall: allowNonManifestPwaInstall);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// GeneralSettings(...).copyWith(id: 12, name: "My name")
/// ```
GeneralSettings call({
Object? themeMode = const $CopyWithPlaceholder(),
Object? uiScaleFactor = const $CopyWithPlaceholder(),
Object? disableAnimations = const $CopyWithPlaceholder(),
Object? showModalBarrier = const $CopyWithPlaceholder(),
Object? enableReadability = const $CopyWithPlaceholder(),
Object? enforceReadability = const $CopyWithPlaceholder(),
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
Object? defaultSearchProvider = const $CopyWithPlaceholder(),
Object? defaultSearchSuggestionsProvider = const $CopyWithPlaceholder(),
Object? createChildTabsOption = const $CopyWithPlaceholder(),
Object? enableLocalAiFeatures = const $CopyWithPlaceholder(),
Object? showContainerUi = const $CopyWithPlaceholder(),
Object? showIsolatedTabUi = const $CopyWithPlaceholder(),
Object? storedDefaultCreateTabType = const $CopyWithPlaceholder(),
Object? newTabPosition = const $CopyWithPlaceholder(),
Object? tabIntentOpenSetting = const $CopyWithPlaceholder(),
Object? autoHideTabBar = const $CopyWithPlaceholder(),
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
Object? historyAutoCleanInterval = const $CopyWithPlaceholder(),
Object? tabViewBottomSheet = const $CopyWithPlaceholder(),
Object? tabBarShowContextualBar = const $CopyWithPlaceholder(),
Object? tabBarShowQuickTabSwitcherBar = const $CopyWithPlaceholder(),
Object? tabBarPosition = const $CopyWithPlaceholder(),
Object? tabBarLayout = const $CopyWithPlaceholder(),
Object? quickTabSwitcherMode = const $CopyWithPlaceholder(),
Object? pullToRefreshEnabled = const $CopyWithPlaceholder(),
Object? useExternalDownloadManager = const $CopyWithPlaceholder(),
Object? doubleBackCloseTab = const $CopyWithPlaceholder(),
Object? unassignedTabsAutoCleanInterval = const $CopyWithPlaceholder(),
Object? maxSearchHistoryEntries = const $CopyWithPlaceholder(),
Object? allowClipboardAccess = const $CopyWithPlaceholder(),
Object? tabListShowFavicons = const $CopyWithPlaceholder(),
Object? quickTabSwitcherShowTitles = const $CopyWithPlaceholder(),
Object? quickTabSwitcherShowHistorySuggestions =
const $CopyWithPlaceholder(),
Object? syncServerOverride = const $CopyWithPlaceholder(),
Object? syncTokenServerOverride = const $CopyWithPlaceholder(),
Object? urlCleanerEnabled = const $CopyWithPlaceholder(),
Object? urlCleanerAutoApply = const $CopyWithPlaceholder(),
Object? urlCleanerAllowReferralMarketing = const $CopyWithPlaceholder(),
Object? urlCleanerCatalogUrl = const $CopyWithPlaceholder(),
Object? urlCleanerHashUrl = const $CopyWithPlaceholder(),
Object? urlCleanerAutoUpdate = const $CopyWithPlaceholder(),
Object? urlCleanerLastCheckEpochMs = const $CopyWithPlaceholder(),
Object? urlCleanerLastUpdateWasAuto = const $CopyWithPlaceholder(),
Object? smallWebTabType = const $CopyWithPlaceholder(),
Object? tabBarLongPressUrlCopy = const $CopyWithPlaceholder(),
Object? unshortenerEnabled = const $CopyWithPlaceholder(),
Object? unshortenerToken = const $CopyWithPlaceholder(),
Object? allowNonManifestPwaInstall = const $CopyWithPlaceholder(),
}) {
return GeneralSettings(
themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null
? _value.themeMode
// ignore: cast_nullable_to_non_nullable
: themeMode as ThemeMode,
uiScaleFactor:
uiScaleFactor == const $CopyWithPlaceholder() || uiScaleFactor == null
? _value.uiScaleFactor
// ignore: cast_nullable_to_non_nullable
: uiScaleFactor as double,
disableAnimations:
disableAnimations == const $CopyWithPlaceholder() ||
disableAnimations == null
? _value.disableAnimations
// ignore: cast_nullable_to_non_nullable
: disableAnimations as bool,
showModalBarrier:
showModalBarrier == const $CopyWithPlaceholder() ||
showModalBarrier == null
? _value.showModalBarrier
// ignore: cast_nullable_to_non_nullable
: showModalBarrier as bool,
enableReadability:
enableReadability == const $CopyWithPlaceholder() ||
enableReadability == null
? _value.enableReadability
// ignore: cast_nullable_to_non_nullable
: enableReadability as bool,
enforceReadability:
enforceReadability == const $CopyWithPlaceholder() ||
enforceReadability == null
? _value.enforceReadability
// ignore: cast_nullable_to_non_nullable
: enforceReadability as bool,
deleteBrowsingDataOnQuit:
deleteBrowsingDataOnQuit == const $CopyWithPlaceholder()
? _value.deleteBrowsingDataOnQuit
// ignore: cast_nullable_to_non_nullable
: deleteBrowsingDataOnQuit as Set<DeleteBrowsingDataType>?,
defaultSearchProvider:
defaultSearchProvider == const $CopyWithPlaceholder()
? _value.defaultSearchProvider
// ignore: cast_nullable_to_non_nullable
: defaultSearchProvider as BangKey?,
defaultSearchSuggestionsProvider:
defaultSearchSuggestionsProvider == const $CopyWithPlaceholder() ||
defaultSearchSuggestionsProvider == null
? _value.defaultSearchSuggestionsProvider
// ignore: cast_nullable_to_non_nullable
: defaultSearchSuggestionsProvider as SearchSuggestionProviders,
createChildTabsOption:
createChildTabsOption == const $CopyWithPlaceholder() ||
createChildTabsOption == null
? _value.createChildTabsOption
// ignore: cast_nullable_to_non_nullable
: createChildTabsOption as bool,
enableLocalAiFeatures:
enableLocalAiFeatures == const $CopyWithPlaceholder() ||
enableLocalAiFeatures == null
? _value.enableLocalAiFeatures
// ignore: cast_nullable_to_non_nullable
: enableLocalAiFeatures as bool,
showContainerUi:
showContainerUi == const $CopyWithPlaceholder() ||
showContainerUi == null
? _value.showContainerUi
// ignore: cast_nullable_to_non_nullable
: showContainerUi as bool,
showIsolatedTabUi:
showIsolatedTabUi == const $CopyWithPlaceholder() ||
showIsolatedTabUi == null
? _value.showIsolatedTabUi
// ignore: cast_nullable_to_non_nullable
: showIsolatedTabUi as bool,
storedDefaultCreateTabType:
storedDefaultCreateTabType == const $CopyWithPlaceholder() ||
storedDefaultCreateTabType == null
? _value.storedDefaultCreateTabType
// ignore: cast_nullable_to_non_nullable
: storedDefaultCreateTabType as TabType,
newTabPosition:
newTabPosition == const $CopyWithPlaceholder() ||
newTabPosition == null
? _value.newTabPosition
// ignore: cast_nullable_to_non_nullable
: newTabPosition as NewTabPosition,
tabIntentOpenSetting:
tabIntentOpenSetting == const $CopyWithPlaceholder() ||
tabIntentOpenSetting == null
? _value.tabIntentOpenSetting
// ignore: cast_nullable_to_non_nullable
: tabIntentOpenSetting as TabIntentOpenSetting,
autoHideTabBar:
autoHideTabBar == const $CopyWithPlaceholder() ||
autoHideTabBar == null
? _value.autoHideTabBar
// ignore: cast_nullable_to_non_nullable
: autoHideTabBar as bool,
tabBarSwipeAction:
tabBarSwipeAction == const $CopyWithPlaceholder() ||
tabBarSwipeAction == null
? _value.tabBarSwipeAction
// ignore: cast_nullable_to_non_nullable
: tabBarSwipeAction as TabBarSwipeAction,
historyAutoCleanInterval:
historyAutoCleanInterval == const $CopyWithPlaceholder() ||
historyAutoCleanInterval == null
? _value.historyAutoCleanInterval
// ignore: cast_nullable_to_non_nullable
: historyAutoCleanInterval as Duration,
tabViewBottomSheet:
tabViewBottomSheet == const $CopyWithPlaceholder() ||
tabViewBottomSheet == null
? _value.tabViewBottomSheet
// ignore: cast_nullable_to_non_nullable
: tabViewBottomSheet as bool,
tabBarShowContextualBar:
tabBarShowContextualBar == const $CopyWithPlaceholder() ||
tabBarShowContextualBar == null
? _value.tabBarShowContextualBar
// ignore: cast_nullable_to_non_nullable
: tabBarShowContextualBar as bool,
tabBarShowQuickTabSwitcherBar:
tabBarShowQuickTabSwitcherBar == const $CopyWithPlaceholder() ||
tabBarShowQuickTabSwitcherBar == null
? _value.tabBarShowQuickTabSwitcherBar
// ignore: cast_nullable_to_non_nullable
: tabBarShowQuickTabSwitcherBar as bool,
tabBarPosition:
tabBarPosition == const $CopyWithPlaceholder() ||
tabBarPosition == null
? _value.tabBarPosition
// ignore: cast_nullable_to_non_nullable
: tabBarPosition as TabBarPosition,
tabBarLayout:
tabBarLayout == const $CopyWithPlaceholder() || tabBarLayout == null
? _value.tabBarLayout
// ignore: cast_nullable_to_non_nullable
: tabBarLayout as TabBarLayout,
quickTabSwitcherMode:
quickTabSwitcherMode == const $CopyWithPlaceholder() ||
quickTabSwitcherMode == null
? _value.quickTabSwitcherMode
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherMode as QuickTabSwitcherMode,
pullToRefreshEnabled:
pullToRefreshEnabled == const $CopyWithPlaceholder() ||
pullToRefreshEnabled == null
? _value.pullToRefreshEnabled
// ignore: cast_nullable_to_non_nullable
: pullToRefreshEnabled as bool,
useExternalDownloadManager:
useExternalDownloadManager == const $CopyWithPlaceholder() ||
useExternalDownloadManager == null
? _value.useExternalDownloadManager
// ignore: cast_nullable_to_non_nullable
: useExternalDownloadManager as bool,
doubleBackCloseTab:
doubleBackCloseTab == const $CopyWithPlaceholder() ||
doubleBackCloseTab == null
? _value.doubleBackCloseTab
// ignore: cast_nullable_to_non_nullable
: doubleBackCloseTab as bool,
unassignedTabsAutoCleanInterval:
unassignedTabsAutoCleanInterval == const $CopyWithPlaceholder() ||
unassignedTabsAutoCleanInterval == null
? _value.unassignedTabsAutoCleanInterval
// ignore: cast_nullable_to_non_nullable
: unassignedTabsAutoCleanInterval as Duration,
maxSearchHistoryEntries:
maxSearchHistoryEntries == const $CopyWithPlaceholder() ||
maxSearchHistoryEntries == null
? _value.maxSearchHistoryEntries
// ignore: cast_nullable_to_non_nullable
: maxSearchHistoryEntries as int,
allowClipboardAccess:
allowClipboardAccess == const $CopyWithPlaceholder() ||
allowClipboardAccess == null
? _value.allowClipboardAccess
// ignore: cast_nullable_to_non_nullable
: allowClipboardAccess as bool,
tabListShowFavicons:
tabListShowFavicons == const $CopyWithPlaceholder() ||
tabListShowFavicons == null
? _value.tabListShowFavicons
// ignore: cast_nullable_to_non_nullable
: tabListShowFavicons as bool,
quickTabSwitcherShowTitles:
quickTabSwitcherShowTitles == const $CopyWithPlaceholder() ||
quickTabSwitcherShowTitles == null
? _value.quickTabSwitcherShowTitles
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherShowTitles as bool,
quickTabSwitcherShowHistorySuggestions:
quickTabSwitcherShowHistorySuggestions ==
const $CopyWithPlaceholder() ||
quickTabSwitcherShowHistorySuggestions == null
? _value.quickTabSwitcherShowHistorySuggestions
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherShowHistorySuggestions as bool,
syncServerOverride:
syncServerOverride == const $CopyWithPlaceholder() ||
syncServerOverride == null
? _value.syncServerOverride
// ignore: cast_nullable_to_non_nullable
: syncServerOverride as String,
syncTokenServerOverride:
syncTokenServerOverride == const $CopyWithPlaceholder() ||
syncTokenServerOverride == null
? _value.syncTokenServerOverride
// ignore: cast_nullable_to_non_nullable
: syncTokenServerOverride as String,
urlCleanerEnabled:
urlCleanerEnabled == const $CopyWithPlaceholder() ||
urlCleanerEnabled == null
? _value.urlCleanerEnabled
// ignore: cast_nullable_to_non_nullable
: urlCleanerEnabled as bool,
urlCleanerAutoApply:
urlCleanerAutoApply == const $CopyWithPlaceholder() ||
urlCleanerAutoApply == null
? _value.urlCleanerAutoApply
// ignore: cast_nullable_to_non_nullable
: urlCleanerAutoApply as bool,
urlCleanerAllowReferralMarketing:
urlCleanerAllowReferralMarketing == const $CopyWithPlaceholder() ||
urlCleanerAllowReferralMarketing == null
? _value.urlCleanerAllowReferralMarketing
// ignore: cast_nullable_to_non_nullable
: urlCleanerAllowReferralMarketing as bool,
urlCleanerCatalogUrl:
urlCleanerCatalogUrl == const $CopyWithPlaceholder() ||
urlCleanerCatalogUrl == null
? _value.urlCleanerCatalogUrl
// ignore: cast_nullable_to_non_nullable
: urlCleanerCatalogUrl as String,
urlCleanerHashUrl:
urlCleanerHashUrl == const $CopyWithPlaceholder() ||
urlCleanerHashUrl == null
? _value.urlCleanerHashUrl
// ignore: cast_nullable_to_non_nullable
: urlCleanerHashUrl as String,
urlCleanerAutoUpdate:
urlCleanerAutoUpdate == const $CopyWithPlaceholder() ||
urlCleanerAutoUpdate == null
? _value.urlCleanerAutoUpdate
// ignore: cast_nullable_to_non_nullable
: urlCleanerAutoUpdate as bool,
urlCleanerLastCheckEpochMs:
urlCleanerLastCheckEpochMs == const $CopyWithPlaceholder()
? _value.urlCleanerLastCheckEpochMs
// ignore: cast_nullable_to_non_nullable
: urlCleanerLastCheckEpochMs as int?,
urlCleanerLastUpdateWasAuto:
urlCleanerLastUpdateWasAuto == const $CopyWithPlaceholder() ||
urlCleanerLastUpdateWasAuto == null
? _value.urlCleanerLastUpdateWasAuto
// ignore: cast_nullable_to_non_nullable
: urlCleanerLastUpdateWasAuto as bool,
smallWebTabType:
smallWebTabType == const $CopyWithPlaceholder() ||
smallWebTabType == null
? _value.smallWebTabType
// ignore: cast_nullable_to_non_nullable
: smallWebTabType as TabType,
tabBarLongPressUrlCopy:
tabBarLongPressUrlCopy == const $CopyWithPlaceholder() ||
tabBarLongPressUrlCopy == null
? _value.tabBarLongPressUrlCopy
// ignore: cast_nullable_to_non_nullable
: tabBarLongPressUrlCopy as bool,
unshortenerEnabled:
unshortenerEnabled == const $CopyWithPlaceholder() ||
unshortenerEnabled == null
? _value.unshortenerEnabled
// ignore: cast_nullable_to_non_nullable
: unshortenerEnabled as bool,
unshortenerToken:
unshortenerToken == const $CopyWithPlaceholder() ||
unshortenerToken == null
? _value.unshortenerToken
// ignore: cast_nullable_to_non_nullable
: unshortenerToken as String,
allowNonManifestPwaInstall:
allowNonManifestPwaInstall == const $CopyWithPlaceholder() ||
allowNonManifestPwaInstall == null
? _value.allowNonManifestPwaInstall
// ignore: cast_nullable_to_non_nullable
: allowNonManifestPwaInstall as bool,
);
}
}
extension $GeneralSettingsCopyWith on GeneralSettings {
/// Returns a callable class used to build a new instance with modified fields.
/// Example: `instanceOfGeneralSettings.copyWith(...)` or `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']),
uiScaleFactor: (json['uiScaleFactor'] as num?)?.toDouble(),
disableAnimations: json['disableAnimations'] as bool?,
showModalBarrier: json['showModalBarrier'] as bool?,
enableReadability: json['enableReadability'] as bool?,
enforceReadability: json['enforceReadability'] as bool?,
deleteBrowsingDataOnQuit: (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
.toSet(),
defaultSearchProvider: const BangKeyConverter().fromJson(
json['defaultSearchProvider'] as String?,
),
defaultSearchSuggestionsProvider: $enumDecodeNullable(
_$SearchSuggestionProvidersEnumMap,
json['defaultSearchSuggestionsProvider'],
),
createChildTabsOption: json['createChildTabsOption'] as bool?,
enableLocalAiFeatures: json['enableLocalAiFeatures'] as bool?,
showContainerUi: json['showContainerUi'] as bool?,
showIsolatedTabUi: json['showIsolatedTabUi'] as bool?,
storedDefaultCreateTabType: $enumDecodeNullable(
_$TabTypeEnumMap,
json['defaultCreateTabType'],
),
newTabPosition: $enumDecodeNullable(
_$NewTabPositionEnumMap,
json['newTabPosition'],
),
tabIntentOpenSetting: $enumDecodeNullable(
_$TabIntentOpenSettingEnumMap,
json['tabIntentOpenSetting'],
),
autoHideTabBar: json['autoHideTabBar'] as bool?,
tabBarSwipeAction: $enumDecodeNullable(
_$TabBarSwipeActionEnumMap,
json['tabBarSwipeAction'],
),
historyAutoCleanInterval: json['historyAutoCleanInterval'] == null
? null
: Duration(
microseconds: (json['historyAutoCleanInterval'] as num).toInt(),
),
tabViewBottomSheet: json['tabViewBottomSheet'] as bool?,
tabBarShowContextualBar: json['tabBarShowContextualBar'] as bool?,
tabBarShowQuickTabSwitcherBar: json['tabBarShowQuickTabSwitcherBar'] as bool?,
tabBarPosition: $enumDecodeNullable(
_$TabBarPositionEnumMap,
json['tabBarPosition'],
),
tabBarLayout: $enumDecodeNullable(
_$TabBarLayoutEnumMap,
json['tabBarLayout'],
),
quickTabSwitcherMode: $enumDecodeNullable(
_$QuickTabSwitcherModeEnumMap,
json['quickTabSwitcherMode'],
),
pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?,
useExternalDownloadManager: json['useExternalDownloadManager'] as bool?,
doubleBackCloseTab: json['doubleBackCloseTab'] as bool?,
unassignedTabsAutoCleanInterval:
json['unassignedTabsAutoCleanInterval'] == null
? null
: Duration(
microseconds: (json['unassignedTabsAutoCleanInterval'] as num)
.toInt(),
),
maxSearchHistoryEntries: (json['maxSearchHistoryEntries'] as num?)?.toInt(),
allowClipboardAccess: json['allowClipboardAccess'] as bool?,
tabListShowFavicons: json['tabListShowFavicons'] as bool?,
quickTabSwitcherShowTitles: json['quickTabSwitcherShowTitles'] as bool?,
quickTabSwitcherShowHistorySuggestions:
json['quickTabSwitcherShowHistorySuggestions'] as bool?,
syncServerOverride: json['syncServerOverride'] as String?,
syncTokenServerOverride: json['syncTokenServerOverride'] as String?,
urlCleanerEnabled: json['urlCleanerEnabled'] as bool?,
urlCleanerAutoApply: json['urlCleanerAutoApply'] as bool?,
urlCleanerAllowReferralMarketing:
json['urlCleanerAllowReferralMarketing'] as bool?,
urlCleanerCatalogUrl: json['urlCleanerCatalogUrl'] as String?,
urlCleanerHashUrl: json['urlCleanerHashUrl'] as String?,
urlCleanerAutoUpdate: json['urlCleanerAutoUpdate'] as bool?,
urlCleanerLastCheckEpochMs: (json['urlCleanerLastCheckEpochMs'] as num?)
?.toInt(),
urlCleanerLastUpdateWasAuto: json['urlCleanerLastUpdateWasAuto'] as bool?,
smallWebTabType: $enumDecodeNullable(
_$TabTypeEnumMap,
json['smallWebTabType'],
),
tabBarLongPressUrlCopy: json['tabBarLongPressUrlCopy'] as bool?,
unshortenerEnabled: json['unshortenerEnabled'] as bool?,
unshortenerToken: json['unshortenerToken'] as String?,
allowNonManifestPwaInstall: json['allowNonManifestPwaInstall'] as bool?,
);
Map<String, dynamic> _$GeneralSettingsToJson(
GeneralSettings instance,
) => <String, dynamic>{
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'uiScaleFactor': instance.uiScaleFactor,
'disableAnimations': instance.disableAnimations,
'showModalBarrier': instance.showModalBarrier,
'enableReadability': instance.enableReadability,
'enforceReadability': instance.enforceReadability,
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
.toList(),
'defaultSearchProvider': const BangKeyConverter().toJson(
instance.defaultSearchProvider,
),
'defaultSearchSuggestionsProvider':
_$SearchSuggestionProvidersEnumMap[instance
.defaultSearchSuggestionsProvider]!,
'createChildTabsOption': instance.createChildTabsOption,
'enableLocalAiFeatures': instance.enableLocalAiFeatures,
'showContainerUi': instance.showContainerUi,
'showIsolatedTabUi': instance.showIsolatedTabUi,
'defaultCreateTabType':
_$TabTypeEnumMap[instance.storedDefaultCreateTabType]!,
'newTabPosition': _$NewTabPositionEnumMap[instance.newTabPosition]!,
'tabIntentOpenSetting':
_$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!,
'autoHideTabBar': instance.autoHideTabBar,
'tabBarSwipeAction': _$TabBarSwipeActionEnumMap[instance.tabBarSwipeAction]!,
'historyAutoCleanInterval': instance.historyAutoCleanInterval.inMicroseconds,
'tabViewBottomSheet': instance.tabViewBottomSheet,
'tabBarShowContextualBar': instance.tabBarShowContextualBar,
'tabBarShowQuickTabSwitcherBar': instance.tabBarShowQuickTabSwitcherBar,
'tabBarPosition': _$TabBarPositionEnumMap[instance.tabBarPosition]!,
'tabBarLayout': _$TabBarLayoutEnumMap[instance.tabBarLayout]!,
'quickTabSwitcherMode':
_$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!,
'pullToRefreshEnabled': instance.pullToRefreshEnabled,
'useExternalDownloadManager': instance.useExternalDownloadManager,
'doubleBackCloseTab': instance.doubleBackCloseTab,
'unassignedTabsAutoCleanInterval':
instance.unassignedTabsAutoCleanInterval.inMicroseconds,
'maxSearchHistoryEntries': instance.maxSearchHistoryEntries,
'allowClipboardAccess': instance.allowClipboardAccess,
'tabListShowFavicons': instance.tabListShowFavicons,
'quickTabSwitcherShowTitles': instance.quickTabSwitcherShowTitles,
'quickTabSwitcherShowHistorySuggestions':
instance.quickTabSwitcherShowHistorySuggestions,
'syncServerOverride': instance.syncServerOverride,
'syncTokenServerOverride': instance.syncTokenServerOverride,
'urlCleanerEnabled': instance.urlCleanerEnabled,
'urlCleanerAutoApply': instance.urlCleanerAutoApply,
'urlCleanerAllowReferralMarketing': instance.urlCleanerAllowReferralMarketing,
'urlCleanerCatalogUrl': instance.urlCleanerCatalogUrl,
'urlCleanerHashUrl': instance.urlCleanerHashUrl,
'urlCleanerAutoUpdate': instance.urlCleanerAutoUpdate,
'urlCleanerLastCheckEpochMs': instance.urlCleanerLastCheckEpochMs,
'urlCleanerLastUpdateWasAuto': instance.urlCleanerLastUpdateWasAuto,
'smallWebTabType': _$TabTypeEnumMap[instance.smallWebTabType]!,
'tabBarLongPressUrlCopy': instance.tabBarLongPressUrlCopy,
'unshortenerEnabled': instance.unshortenerEnabled,
'unshortenerToken': instance.unshortenerToken,
'allowNonManifestPwaInstall': instance.allowNonManifestPwaInstall,
};
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',
};
const _$SearchSuggestionProvidersEnumMap = {
SearchSuggestionProviders.none: 'none',
SearchSuggestionProviders.brave: 'brave',
SearchSuggestionProviders.ddg: 'ddg',
SearchSuggestionProviders.kagi: 'kagi',
SearchSuggestionProviders.qwant: 'qwant',
};
const _$TabTypeEnumMap = {
TabType.regular: 'regular',
TabType.private: 'private',
TabType.child: 'child',
TabType.isolated: 'isolated',
};
const _$NewTabPositionEnumMap = {
NewTabPosition.first: 'first',
NewTabPosition.end: 'end',
};
const _$TabIntentOpenSettingEnumMap = {
TabIntentOpenSetting.regular: 'regular',
TabIntentOpenSetting.private: 'private',
TabIntentOpenSetting.ask: 'ask',
};
const _$TabBarSwipeActionEnumMap = {
TabBarSwipeAction.switchLastOpened: 'switchLastOpened',
TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs',
};
const _$TabBarPositionEnumMap = {
TabBarPosition.top: 'top',
TabBarPosition.bottom: 'bottom',
};
const _$TabBarLayoutEnumMap = {
TabBarLayout.withTitle: 'withTitle',
TabBarLayout.compact: 'compact',
};
const _$QuickTabSwitcherModeEnumMap = {
QuickTabSwitcherMode.lastUsedTabs: 'lastUsedTabs',
QuickTabSwitcherMode.containerTabs: 'containerTabs',
};
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'rfp_target.g.dart';
@JsonSerializable()
class RFPTarget with FastEquatable {
final String name;
final int id;
final String? description;
final List<String> keywords;
RFPTarget({
required this.name,
required this.id,
this.description,
required this.keywords,
});
factory RFPTarget.fromJson(Map<String, dynamic> json) =>
_$RFPTargetFromJson(json);
Map<String, dynamic> toJson() => _$RFPTargetToJson(this);
@override
List<Object?> get hashParameters => [name, id, description, keywords];
}
@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'rfp_target.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
RFPTarget _$RFPTargetFromJson(Map<String, dynamic> json) => RFPTarget(
name: json['name'] as String,
id: (json['id'] as num).toInt(),
description: json['description'] as String?,
keywords: (json['keywords'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$RFPTargetToJson(RFPTarget instance) => <String, dynamic>{
'name': instance.name,
'id': instance.id,
'description': instance.description,
'keywords': instance.keywords,
};
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'tor_settings.g.dart';
enum TorConnectionConfig { auto, direct, obfs4, snowflake }
enum TorRegularTabProxyMode { container, all }
@CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
class TorSettings with FastEquatable {
final TorRegularTabProxyMode proxyRegularTabsMode;
final bool proxyPrivateTabsTor;
final TorConnectionConfig config;
final bool requireBridge;
final bool fetchRemoteBridges;
final String? entryNodeCountry;
final String? exitNodeCountry;
TorSettings({
required this.proxyRegularTabsMode,
required this.proxyPrivateTabsTor,
required this.config,
required this.requireBridge,
required this.fetchRemoteBridges,
required this.entryNodeCountry,
required this.exitNodeCountry,
});
TorSettings.withDefaults({
TorRegularTabProxyMode? proxyRegularTabsMode,
bool? proxyPrivateTabsTor,
TorConnectionConfig? config,
bool? requireBridge,
bool? fetchRemoteBridges,
this.entryNodeCountry,
this.exitNodeCountry,
}) : proxyRegularTabsMode =
proxyRegularTabsMode ?? TorRegularTabProxyMode.container,
proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
config = config ?? TorConnectionConfig.auto,
requireBridge = requireBridge ?? false,
fetchRemoteBridges = fetchRemoteBridges ?? true;
factory TorSettings.fromJson(Map<String, dynamic> json) =>
_$TorSettingsFromJson(json);
Map<String, dynamic> toJson() => _$TorSettingsToJson(this);
@override
List<Object?> get hashParameters => [
proxyRegularTabsMode,
proxyPrivateTabsTor,
config,
requireBridge,
fetchRemoteBridges,
entryNodeCountry,
exitNodeCountry,
];
}
@@ -0,0 +1,181 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tor_settings.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$TorSettingsCWProxy {
TorSettings proxyRegularTabsMode(TorRegularTabProxyMode proxyRegularTabsMode);
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor);
TorSettings config(TorConnectionConfig config);
TorSettings requireBridge(bool requireBridge);
TorSettings fetchRemoteBridges(bool fetchRemoteBridges);
TorSettings entryNodeCountry(String? entryNodeCountry);
TorSettings exitNodeCountry(String? exitNodeCountry);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `TorSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ```
TorSettings call({
TorRegularTabProxyMode proxyRegularTabsMode,
bool proxyPrivateTabsTor,
TorConnectionConfig config,
bool requireBridge,
bool fetchRemoteBridges,
String? entryNodeCountry,
String? exitNodeCountry,
});
}
/// Callable proxy for `copyWith` functionality.
/// Use as `instanceOfTorSettings.copyWith(...)` or call `instanceOfTorSettings.copyWith.fieldName(value)` for a single field.
class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
const _$TorSettingsCWProxyImpl(this._value);
final TorSettings _value;
@override
TorSettings proxyRegularTabsMode(
TorRegularTabProxyMode proxyRegularTabsMode,
) => call(proxyRegularTabsMode: proxyRegularTabsMode);
@override
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) =>
call(proxyPrivateTabsTor: proxyPrivateTabsTor);
@override
TorSettings config(TorConnectionConfig config) => call(config: config);
@override
TorSettings requireBridge(bool requireBridge) =>
call(requireBridge: requireBridge);
@override
TorSettings fetchRemoteBridges(bool fetchRemoteBridges) =>
call(fetchRemoteBridges: fetchRemoteBridges);
@override
TorSettings entryNodeCountry(String? entryNodeCountry) =>
call(entryNodeCountry: entryNodeCountry);
@override
TorSettings exitNodeCountry(String? exitNodeCountry) =>
call(exitNodeCountry: exitNodeCountry);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `TorSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ```
TorSettings call({
Object? proxyRegularTabsMode = const $CopyWithPlaceholder(),
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? config = const $CopyWithPlaceholder(),
Object? requireBridge = const $CopyWithPlaceholder(),
Object? fetchRemoteBridges = const $CopyWithPlaceholder(),
Object? entryNodeCountry = const $CopyWithPlaceholder(),
Object? exitNodeCountry = const $CopyWithPlaceholder(),
}) {
return TorSettings(
proxyRegularTabsMode:
proxyRegularTabsMode == const $CopyWithPlaceholder() ||
proxyRegularTabsMode == null
? _value.proxyRegularTabsMode
// ignore: cast_nullable_to_non_nullable
: proxyRegularTabsMode as TorRegularTabProxyMode,
proxyPrivateTabsTor:
proxyPrivateTabsTor == const $CopyWithPlaceholder() ||
proxyPrivateTabsTor == null
? _value.proxyPrivateTabsTor
// ignore: cast_nullable_to_non_nullable
: proxyPrivateTabsTor as bool,
config: config == const $CopyWithPlaceholder() || config == null
? _value.config
// ignore: cast_nullable_to_non_nullable
: config as TorConnectionConfig,
requireBridge:
requireBridge == const $CopyWithPlaceholder() || requireBridge == null
? _value.requireBridge
// ignore: cast_nullable_to_non_nullable
: requireBridge as bool,
fetchRemoteBridges:
fetchRemoteBridges == const $CopyWithPlaceholder() ||
fetchRemoteBridges == null
? _value.fetchRemoteBridges
// ignore: cast_nullable_to_non_nullable
: fetchRemoteBridges as bool,
entryNodeCountry: entryNodeCountry == const $CopyWithPlaceholder()
? _value.entryNodeCountry
// ignore: cast_nullable_to_non_nullable
: entryNodeCountry as String?,
exitNodeCountry: exitNodeCountry == const $CopyWithPlaceholder()
? _value.exitNodeCountry
// ignore: cast_nullable_to_non_nullable
: exitNodeCountry as String?,
);
}
}
extension $TorSettingsCopyWith on TorSettings {
/// Returns a callable class used to build a new instance with modified fields.
/// Example: `instanceOfTorSettings.copyWith(...)` or `instanceOfTorSettings.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$TorSettingsCWProxy get copyWith => _$TorSettingsCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
TorSettings.withDefaults(
proxyRegularTabsMode: $enumDecodeNullable(
_$TorRegularTabProxyModeEnumMap,
json['proxyRegularTabsMode'],
),
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']),
requireBridge: json['requireBridge'] as bool?,
fetchRemoteBridges: json['fetchRemoteBridges'] as bool?,
entryNodeCountry: json['entryNodeCountry'] as String?,
exitNodeCountry: json['exitNodeCountry'] as String?,
);
Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) =>
<String, dynamic>{
'proxyRegularTabsMode':
_$TorRegularTabProxyModeEnumMap[instance.proxyRegularTabsMode]!,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
'config': _$TorConnectionConfigEnumMap[instance.config]!,
'requireBridge': instance.requireBridge,
'fetchRemoteBridges': instance.fetchRemoteBridges,
'entryNodeCountry': instance.entryNodeCountry,
'exitNodeCountry': instance.exitNodeCountry,
};
const _$TorRegularTabProxyModeEnumMap = {
TorRegularTabProxyMode.container: 'container',
TorRegularTabProxyMode.all: 'all',
};
const _$TorConnectionConfigEnumMap = {
TorConnectionConfig.auto: 'auto',
TorConnectionConfig.direct: 'direct',
TorConnectionConfig.obfs4: 'obfs4',
TorConnectionConfig.snowflake: 'snowflake',
};
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:riverpod/experimental/persist.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
import 'package:weblibre/core/database_registry.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/riverpod_storage.dart';
part 'providers.g.dart';
@Riverpod(keepAlive: true)
UserDatabase userDatabase(Ref ref) {
final db = UserDatabase(
LazyDatabase(() async {
final file = File(p.join(filesystem.profileDatabasesDir.path, 'user.db'));
// Also work around limitations on old Android versions
if (Platform.isAndroid) {
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
}
return NativeDatabase.createInBackground(
file,
setup: (database) {
registerLexorankFunctions(database);
},
);
}),
);
DatabaseRegistry.instance.register('user', db);
ref.onDispose(() async {
await db.close();
});
return db;
}
@Riverpod(keepAlive: true)
Storage<String, String> riverpodDatabaseStorage(Ref ref) {
final db = ref.watch(userDatabaseProvider);
return RiverpodStorage(db);
}
@@ -0,0 +1,99 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(userDatabase)
final userDatabaseProvider = UserDatabaseProvider._();
final class UserDatabaseProvider
extends $FunctionalProvider<UserDatabase, UserDatabase, UserDatabase>
with $Provider<UserDatabase> {
UserDatabaseProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'userDatabaseProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$userDatabaseHash();
@$internal
@override
$ProviderElement<UserDatabase> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
UserDatabase create(Ref ref) {
return userDatabase(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(UserDatabase value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<UserDatabase>(value),
);
}
}
String _$userDatabaseHash() => r'097505438c098252a322e6d0e49f885d67ebe898';
@ProviderFor(riverpodDatabaseStorage)
final riverpodDatabaseStorageProvider = RiverpodDatabaseStorageProvider._();
final class RiverpodDatabaseStorageProvider
extends
$FunctionalProvider<
Storage<String, String>,
Storage<String, String>,
Storage<String, String>
>
with $Provider<Storage<String, String>> {
RiverpodDatabaseStorageProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'riverpodDatabaseStorageProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$riverpodDatabaseStorageHash();
@$internal
@override
$ProviderElement<Storage<String, String>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
Storage<String, String> create(Ref ref) {
return riverpodDatabaseStorage(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Storage<String, String> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Storage<String, String>>(value),
);
}
}
String _$riverpodDatabaseStorageHash() =>
r'e60613538dcb1f9cf48d87c029d8493335a5c3e8';
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class AuthException implements Exception {
final String message;
AuthException(this.message);
@override
String toString() {
return message;
}
}
@@ -0,0 +1,193 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:exceptions/exceptions.dart';
import 'package:fast_equatable/fast_equatable.dart';
class FingerprintOverrides with FastEquatable {
static final pattern = RegExp('([+-])([a-zA-Z_][a-zA-Z0-9_]{1,64})');
final bool? allTargets;
final Map<String, bool> targets;
FingerprintOverrides(this.allTargets, this.targets);
//Monitor https://searchfox.org/firefox-main/source/toolkit/components/resistfingerprinting/RFPTargetsDefault.inc
FingerprintOverrides.defaults()
: this(false, {
'CanvasRandomization': true,
'EfficientCanvasRandomization': true,
'FontVisibilityLangPack': true,
'JSMathFdlibm': true,
'ScreenAvailToResolution': true,
'NavigatorHWConcurrencyTiered': true,
'MaxTouchPointsCollapse': true,
});
FingerprintOverrides.hardenedDefaults()
: this(false, {
'TouchEvents': true,
'PointerEvents': true,
'KeyboardEvents': true,
'ScreenOrientation': true,
'SpeechSynthesis': true,
'CSSPrefersReducedMotion': true,
'CSSPrefersContrast': true,
'CanvasRandomization': true,
'CanvasExtractionFromThirdPartiesIsBlocked': true,
'JSLocale': true,
'NavigatorAppVersion': true,
'NavigatorBuildID': true,
'NavigatorHWConcurrency': true,
'NavigatorOscpu': true,
'NavigatorPlatform': true,
'NavigatorUserAgent': true,
'PointerId': true,
'StreamVideoFacingMode': true,
'JSDateTimeUTC': true,
'JSMathFdlibm': true,
'Gamepad': true,
'HttpUserAgent': true,
'WindowOuterSize': true,
'WindowScreenXY': true,
'WindowInnerScreenXY': true,
'ScreenPixelDepth': true,
'ScreenRect': true,
'ScreenAvailRect': true,
'VideoElementMozFrames': true,
'VideoElementMozFrameDelay': true,
'VideoElementPlaybackQuality': true,
'ReduceTimerPrecision': true,
'WidgetEvents': true,
'MediaDevices': true,
'MediaCapabilities': true,
'AudioSampleRate': true,
'NetworkConnection': true,
'WindowDevicePixelRatio': true,
'MouseEventScreenPoint': true,
'FontVisibilityBaseSystem': true,
'FontVisibilityLangPack': true,
'DeviceSensors': true,
'RoundWindowSize': true,
'UseStandinsForNativeColors': true,
'AudioContext': true,
'MediaError': true,
'DOMStyleOsxFontSmoothing': true,
'CSSDeviceSize': true,
'CSSColorInfo': true,
'CSSResolution': true,
'CSSPrefersReducedTransparency': true,
'CSSInvertedColors': true,
'CSSVideoDynamicRange': true,
'CSSPointerCapabilities': true,
'WebGLRenderCapability': true,
'WebGLRenderInfo': true,
'SiteSpecificZoom': true,
'FontVisibilityRestrictGenerics': true,
'WebVTT': true,
'WebGPULimits': true,
'WebGPUIsFallbackAdapter': true,
'WebGPUSubgroupSizes': true,
'JSLocalePrompt': true,
'ScreenAvailToResolution': true,
'UseHardcodedFontSubstitutes': true,
'DiskStorageLimit': true,
'WebCodecs': true,
'MaxTouchPoints': true,
'MaxTouchPointsCollapse': true,
'NavigatorHWConcurrencyTiered': true,
});
static Result<FingerprintOverrides> parse(
String input,
Set<String> availableTargets,
) {
final cleaned = input.replaceAll(RegExp(r'\s'), '');
if (cleaned.isEmpty) return Result.success(FingerprintOverrides(null, {}));
bool? allTargets;
final targets = <String, bool>{};
for (final word in cleaned.split(',')) {
final match = pattern.firstMatch(word);
if (match == null) {
return Result.failure(
const ErrorMessage(source: 'FpParser', message: 'Invalid Override'),
);
}
final enabled = match.group(1) != '-';
final name = match.group(2)!;
if (name == 'AllTargets') {
allTargets = enabled;
continue;
}
if (!availableTargets.contains(name)) {
return Result.failure(
const ErrorMessage(
source: 'FpParser',
message: 'Invalid target name',
),
);
}
targets[name] = enabled;
}
return Result.success(FingerprintOverrides(allTargets, targets));
}
@override
String toString() {
final sb = StringBuffer();
if (allTargets != null) {
sb.write('${allTargets! ? '+' : '-'}AllTargets');
if (targets.isNotEmpty) {
sb.write(',');
}
}
sb.write(
targets.entries.map((e) => (e.value ? '+' : '-') + e.key).join(','),
);
return sb.toString();
}
FingerprintOverrides copyWithAllTargetsEnabled(bool value) {
return FingerprintOverrides(
value,
Map.fromEntries(targets.entries.where((e) => e.value != value)),
);
}
FingerprintOverrides copyWithTarget(String name, bool value) {
if (value && allTargets == true) {
return this;
}
return FingerprintOverrides(allTargets, {...targets, name: value});
}
@override
List<Object?> get hashParameters => [allTargets, targets];
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
/// Dialog to confirm profile/user deletion.
/// Returns true if user confirms deletion, false if cancelled, null if dismissed.
Future<bool?> showDeleteProfileDialog(BuildContext context) {
return showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
icon: const Icon(Icons.warning),
title: const Text('Delete User'),
content: const Text(
'Are you sure you want to delete this User including all data?',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Delete'),
),
],
);
},
);
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
/// Dialog to confirm overriding existing profile during restore.
/// Returns true if user confirms override, false if cancelled, null if dismissed.
Future<bool?> showOverrideProfileDialog(BuildContext context) {
return showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
icon: const Icon(Icons.warning),
title: const Text('Override User'),
content: const Text(
'Are you sure you want to override the exisiting User?',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Override'),
),
],
);
},
);
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
/// Dialog to confirm password during backup creation.
/// Returns the entered password string if confirmed, null if cancelled or dismissed.
Future<String?> showPasswordConfirmationDialog(BuildContext context) {
return showDialog<String>(
context: context,
builder: (context) {
final controller = TextEditingController();
return AlertDialog(
title: const Text('Password Confirmation'),
content: TextField(
controller: controller,
enableSuggestions: false,
autocorrect: false,
enableIMEPersonalizedLearning: false,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(controller.text);
},
child: const Text('Confirm'),
),
],
);
},
);
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
/// Shows a confirmation dialog for quitting the browser.
///
/// Returns true if the user confirms, false if cancelled, null if dismissed.
Future<bool?> showQuitBrowserDialog(BuildContext context) {
return showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
icon: const Icon(Icons.warning),
title: const Text('Quit Browser'),
content: const Text(
'This will properly shutdown the browser and clear private tabs',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Quit'),
),
],
);
},
);
}
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/user/domain/presentation/utils/profile_switch_handler.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
/// Bottom sheet widget to select a user profile.
class SelectProfileDialog extends HookConsumerWidget {
const SelectProfileDialog({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(profileRepositoryProvider);
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Select user',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
usersAsync.when(
skipLoadingOnReload: true,
data: (profiles) => Wrap(
alignment: WrapAlignment.center,
spacing: 24,
runSpacing: 16,
children: [
...profiles.map(
(profile) => _ProfileAvatar(
profile: profile,
isActive: filesystem.selectedProfile == profile.uuidValue,
onTap: () async {
await handleSwitchProfile(context, ref, profile);
},
onLongPress: () async {
await EditProfileRoute(
profile: jsonEncode(profile.toJson()),
).push(context);
},
),
),
_AddProfileAvatar(
onTap: () async {
await CreateProfileRoute().push(context);
},
),
],
),
error: (error, stackTrace) => Center(
child: FailureWidget(
title: 'Failed to load Profiles',
exception: error,
),
),
loading: () => const Center(child: CircularProgressIndicator()),
),
const SizedBox(height: 24),
TextButton.icon(
onPressed: () async {
await ProfileListRoute().push(context);
},
icon: const Icon(MdiIcons.accountGroup),
label: const Text('Manage Profiles'),
),
],
),
),
);
}
}
class _ProfileAvatar extends StatelessWidget {
final Profile profile;
final bool isActive;
final VoidCallback onTap;
final VoidCallback onLongPress;
const _ProfileAvatar({
required this.profile,
required this.isActive,
required this.onTap,
required this.onLongPress,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
onLongPress: onLongPress,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 28,
backgroundColor: isActive
? colorScheme.primary
: colorScheme.surfaceContainerHighest,
child: Icon(
Icons.person,
size: 24,
color: isActive
? colorScheme.onPrimary
: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
SizedBox(
width: 72,
child: Text(
profile.name,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
class _AddProfileAvatar extends StatelessWidget {
final VoidCallback onTap;
const _AddProfileAvatar({required this.onTap});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.transparent,
foregroundColor: colorScheme.onSurfaceVariant,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: colorScheme.outline),
),
child: Center(
child: Icon(
Icons.add,
size: 24,
color: colorScheme.onSurfaceVariant,
),
),
),
),
const SizedBox(height: 8),
SizedBox(
width: 72,
child: Text(
'Add user',
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
/// Shows a confirmation dialog for switching user profiles.
///
/// Returns `true` if the user confirms the switch, or null if dismissed.
Future<bool?> showSwitchProfileDialog(
BuildContext context, {
required String profileName,
}) {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
icon: const Icon(Icons.warning),
title: const Text('Switch User'),
content: Text(
"Switching to User '$profileName' will require a restart of the Browser.",
style: const TextStyle(fontWeight: FontWeight.bold),
),
actions: [
TextButton(
onPressed: () {
context.pop(false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
context.pop(true);
},
child: const Text('Switch Profile'),
),
],
),
);
}
@@ -0,0 +1,205 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fancy_password_field/fancy_password_field.dart';
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:hooks_riverpod/hooks_riverpod.dart';
import 'package:saf_util/saf_util.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/user/domain/presentation/dialogs/password_confirmation_dialog.dart';
import 'package:weblibre/features/user/domain/providers/backup_directory.dart';
import 'package:weblibre/features/user/domain/services/user_backup.dart';
import 'package:weblibre/utils/ui_helper.dart';
class ProfileBackupScreen extends HookConsumerWidget {
final Profile profile;
const ProfileBackupScreen({super.key, required this.profile});
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final passwordTextController = useTextEditingController();
final passwordController = useMemoized(() => FancyPasswordController());
final integrityVerification = useState(true);
final skipCaches = useState(false);
final skipPasswordConfirmation = useState(false);
final backupFuture = useState<Future<bool>?>(null);
final backupState = useFuture(backupFuture.value);
useEffect(() {
if (backupState.hasError) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showErrorMessage(context, backupState.error!.toString());
});
} else if (backupState.hasData) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showInfoMessage(context, 'Backup created successfully');
ProfileListRoute().go(context);
});
}
return null;
}, [backupState.hasError, backupState.hasData, backupState.error]);
final disableInteraction =
backupState.connectionState == ConnectionState.waiting;
return Scaffold(
appBar: AppBar(title: const Text('Create Backup')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Form(
key: formKey,
child: ListView(
children: [
FancyPasswordField(
controller: passwordTextController,
enabled: !disableInteraction,
passwordController: passwordController,
enableSuggestions: false,
autocorrect: false,
enableIMEPersonalizedLearning: false,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
labelText: 'Password',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
validationRules: {MinCharactersValidationRule(5)},
validator: (value) {
//Make sure since onChange is sometimes unreliable
passwordController.onChange(value ?? '');
return passwordController.areAllRulesValidated
? null
: 'Not Validated';
},
),
const SizedBox(height: 16),
SwitchListTile(
contentPadding: EdgeInsets.zero,
value: integrityVerification.value,
onChanged: disableInteraction
? null
: (value) {
integrityVerification.value = value;
},
title: const Text('Verify Backup Integrity'),
subtitle: const Text(
'Automatically check that backups are complete and restorable',
),
),
SwitchListTile(
contentPadding: EdgeInsets.zero,
value: skipCaches.value,
onChanged: disableInteraction
? null
: (value) {
skipCaches.value = value;
},
title: const Text('Skip Cache Directories'),
subtitle: const Text(
'Leave out temporary browser caches like page, icon, and thumbnail data to keep backups smaller',
),
),
ExpansionTile(
enabled: !disableInteraction,
childrenPadding: EdgeInsets.zero,
tilePadding: EdgeInsets.zero,
title: const Text('Advanced'),
children: [
SwitchListTile(
contentPadding: EdgeInsets.zero,
value: skipPasswordConfirmation.value,
onChanged: disableInteraction
? null
: (value) {
skipPasswordConfirmation.value = value;
},
title: const Text('Skip Password Confirmation Prompt'),
),
],
),
const SizedBox(height: 16),
if (disableInteraction)
const Column(
children: [
LinearProgressIndicator(),
Text('Creating Backup'),
],
)
else
FilledButton.icon(
icon: const Icon(MdiIcons.safe),
onPressed: () async {
if (formKey.currentState?.validate() ?? false) {
if (!skipPasswordConfirmation.value) {
final confirmation =
await showPasswordConfirmationDialog(context);
if (confirmation != passwordTextController.text) {
if (context.mounted) {
showErrorMessage(
context,
'Passwords do not match',
);
}
return;
}
}
if (ref.read(backupDirectoryUriProvider) == null) {
final dir = await SafUtil().pickDirectory(
writePermission: true,
persistablePermission: true,
);
if (dir == null) return;
ref
.read(backupDirectoryUriProvider.notifier)
.set(Uri.parse(dir.uri));
}
backupFuture.value = ref
.read(userBackupServiceProvider.notifier)
.createUserBackup(
profile,
password: passwordTextController.text,
integrityCheck: integrityVerification.value,
skipCaches: skipCaches.value,
);
}
},
label: const Text('Backup'),
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,163 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:saf_util/saf_util.dart';
import 'package:weblibre/core/providers/format.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/user/domain/providers.dart';
import 'package:weblibre/features/user/domain/providers/backup_directory.dart';
import 'package:weblibre/features/user/domain/services/user_backup.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
final _filenamePattern = RegExp(
r'^backup_(?<profile>.+?)_(?<timestamp>\d{4}-\d{2}-\d{2}_\d{6})\.weblibre$',
);
class ProfileBackupListScreen extends HookConsumerWidget {
const ProfileBackupListScreen({super.key});
Future<void> _pickDirectory(WidgetRef ref) async {
final dir = await SafUtil().pickDirectory(
writePermission: true,
persistablePermission: true,
);
if (dir != null) {
final dirUri = Uri.parse(dir.uri);
ref.read(backupDirectoryUriProvider.notifier).set(dirUri);
final migrated = await ref
.read(userBackupServiceProvider.notifier)
.migrateOldBackups(dirUri);
if (migrated > 0) {
ref.invalidate(backupListProvider);
}
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final dirUri = ref.watch(backupDirectoryUriProvider);
final backupListAsync = ref.watch(backupListProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Backups'),
actions: [
IconButton(
icon: const Icon(MdiIcons.folderCog),
tooltip: 'Change backup directory',
onPressed: () => _pickDirectory(ref),
),
],
),
body: SafeArea(
child: dirUri == null
? Center(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(MdiIcons.folderOpen, size: 64),
const SizedBox(height: 16),
const Text(
'Select a directory to store your backups.',
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const Text(
'Choose a location outside of the app to keep your backups safe across reinstalls.',
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
FilledButton.icon(
icon: const Icon(MdiIcons.folderPlus),
label: const Text('Select Backup Directory'),
onPressed: () => _pickDirectory(ref),
),
],
),
),
)
: backupListAsync.when(
data: (backupList) {
if (backupList.isEmpty) {
return const Center(child: Text('No backups found'));
}
return ListView.builder(
itemCount: backupList.length,
itemBuilder: (context, index) {
final file = backupList[index];
final match = _filenamePattern.firstMatch(file.name);
if (match != null) {
final profileName = match.group(1)!;
final datePart = match.group(2)!;
final dateTime = UserBackupService.dateFormatter.decode(
datePart,
);
return ListTile(
key: ValueKey(file.uri),
title: Text(profileName),
subtitle: Text(
ref
.read(formatProvider.notifier)
.fullDateTime(dateTime),
),
onTap: () async {
await RestoreProfileRoute(
backupFileUri: file.uri,
).push(context);
},
);
} else {
return ListTile(
key: ValueKey(file.uri),
title: Text(file.name),
onTap: () async {
await RestoreProfileRoute(
backupFileUri: file.uri,
).push(context);
},
);
}
},
);
},
error: (error, stackTrace) => FailureWidget(
title: 'Failed to get backups',
exception: error,
onRetry: () {
ref.invalidate(backupListProvider);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
),
),
);
}
}
@@ -0,0 +1,320 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package: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:weblibre/core/filesystem.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/settings/presentation/widgets/sections.dart';
import 'package:weblibre/features/user/data/models/auth_settings.dart';
import 'package:weblibre/features/user/domain/presentation/dialogs/delete_profile_dialog.dart';
import 'package:weblibre/features/user/domain/presentation/utils/profile_switch_handler.dart';
import 'package:weblibre/features/user/domain/providers/profile_auth.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/features/user/domain/services/local_authentication.dart';
import 'package:weblibre/utils/form_validators.dart';
const _timeoutOptions = <DropdownMenuItem<Duration?>>[
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 ProfileEditScreen extends HookConsumerWidget {
final Profile? profile;
const ProfileEditScreen({super.key, required this.profile});
Future<void> _handleSave(
BuildContext context,
WidgetRef ref,
GlobalKey<FormState> formKey,
String name,
AuthSettings authSettings,
) async {
if (!(formKey.currentState?.validate() ?? false)) {
return;
}
// Require biometric confirmation when enabling/changing auth
if (profile != null &&
(profile!.authSettings.authenticationRequired ||
authSettings.authenticationRequired)) {
final authResult = await ref
.read(localAuthenticationServiceProvider.notifier)
.authenticate(
authKey: profileAccessAuthKey(profile!.id),
localizedReason: 'Require authentication for profile',
);
if (!authResult) {
return;
}
}
if (profile != null) {
await ref
.read(profileRepositoryProvider.notifier)
.updateProfileMetadata(
profile!.copyWith(name: name, authSettings: authSettings),
);
if (context.mounted) {
context.pop();
}
} else {
await ref
.read(profileRepositoryProvider.notifier)
.createProfile(name: name, authSettings: authSettings);
if (context.mounted) {
context.pop();
}
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final nameTextController = useTextEditingController(text: profile?.name);
final authSettings = useState(
profile?.authSettings ?? AuthSettings.withDefaults(),
);
return Scaffold(
appBar: AppBar(
title: (profile != null)
? const Text('Edit User')
: const Text('Create User'),
actions: [
IconButton(
onPressed: () async {
await _handleSave(
context,
ref,
formKey,
nameTextController.text,
authSettings.value,
);
},
icon: const Icon(Icons.check),
),
],
),
body: SafeArea(
child: Form(
key: formKey,
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
children: [
TextFormField(
controller: nameTextController,
decoration: const InputDecoration(
label: Text('Name'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
validator: validateProfileName,
),
const SizedBox(height: 24),
_AuthSection(
authSettings: authSettings.value,
onAuthSettingsChanged: (newSettings) {
authSettings.value = newSettings;
},
),
const SizedBox(height: 24),
if (profile != null) ...[
_ProfileActionsSection(profile: profile!),
],
],
),
),
),
);
}
}
class _AuthSection extends StatelessWidget {
final AuthSettings authSettings;
final ValueChanged<AuthSettings> onAuthSettingsChanged;
const _AuthSection({
required this.authSettings,
required this.onAuthSettingsChanged,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SettingSection(name: 'Authentication'),
SwitchListTile.adaptive(
value: authSettings.authenticationRequired,
title: const Text('Require Authentication'),
subtitle: const Text(
'Lock this profile when switching away from the app',
),
secondary: const Icon(MdiIcons.fingerprint),
contentPadding: EdgeInsets.zero,
onChanged: (value) {
onAuthSettingsChanged(
authSettings.copyWith.authenticationRequired(value),
);
},
),
if (authSettings.authenticationRequired) ...[
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Auto-lock Behavior'),
subtitle: Text('Choose when to lock the profile'),
contentPadding: EdgeInsets.zero,
leading: Icon(MdiIcons.lockClock),
),
RadioGroup<AutoLockMode>(
groupValue: authSettings.autoLockMode,
onChanged: (value) {
if (value != null) {
onAuthSettingsChanged(
authSettings.copyWith.autoLockMode(value),
);
}
},
child: const Column(
children: [
RadioListTile.adaptive(
value: AutoLockMode.background,
title: Text('Lock on Background'),
subtitle: Text(
'Lock immediately when app goes to background',
),
),
RadioListTile.adaptive(
value: AutoLockMode.timeout,
title: Text('Lock After Timeout'),
subtitle: Text('Lock after a period of inactivity'),
),
],
),
),
],
),
),
if (authSettings.autoLockMode == AutoLockMode.timeout)
ListTile(
title: const Text('Timeout Duration'),
subtitle: const Text('How long to wait before locking'),
leading: const Icon(MdiIcons.timerOutline),
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
trailing: DropdownButton<Duration?>(
value: authSettings.timeout,
items: _timeoutOptions,
underline: const SizedBox.shrink(),
onChanged: (Duration? value) {
if (value != null) {
onAuthSettingsChanged(authSettings.copyWith.timeout(value));
}
},
),
),
],
],
);
}
}
class _ProfileActionsSection extends ConsumerWidget {
final Profile profile;
const _ProfileActionsSection({required this.profile});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SettingSection(name: 'Profile Actions'),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
label: const Text('Backup'),
icon: const Icon(MdiIcons.safe),
onPressed: () async {
await BackupProfileRoute(
profile: jsonEncode(profile.toJson()),
).push(context);
},
),
),
const SizedBox(height: 12),
if (filesystem.selectedProfile != profile.uuidValue)
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
label: const Text('Switch to this Profile'),
icon: const Icon(MdiIcons.accountSwitch),
onPressed: () async {
await handleSwitchProfile(context, ref, profile);
},
),
),
if (filesystem.selectedProfile != profile.uuidValue)
const SizedBox(height: 12),
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 showDeleteProfileDialog(context);
if (result == true) {
await ref
.read(profileRepositoryProvider.notifier)
.deleteProfile(profile.uuidValue.uuid);
if (context.mounted) {
context.pop();
}
}
},
),
),
],
);
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
class ProfileListScreen extends HookConsumerWidget {
const ProfileListScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(profileRepositoryProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Users'),
actions: [
IconButton(
onPressed: () async {
await ProfileBackupListRoute().push(context);
},
icon: const Icon(MdiIcons.backupRestore),
),
],
),
body: SafeArea(
child: usersAsync.when(
skipLoadingOnReload: true,
data: (profiles) => ListView.builder(
itemCount: profiles.length,
itemBuilder: (context, index) {
final profile = profiles[index];
final isSelected =
filesystem.selectedProfile == profile.uuidValue;
return ListTile(
enabled: !isSelected,
leading: const Icon(Icons.person),
title: Text(profile.name),
subtitle: isSelected ? const Text('Active') : null,
trailing: const Icon(Icons.chevron_right),
onTap: () async {
await EditProfileRoute(
profile: jsonEncode(profile.toJson()),
).push(context);
},
);
},
),
error: (error, stackTrace) => Center(
child: FailureWidget(
title: 'Failed to load Profiles',
exception: error,
),
),
loading: () => const Center(child: CircularProgressIndicator()),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await CreateProfileRoute().push(context);
},
child: const Icon(Icons.person_add),
),
);
}
}
@@ -0,0 +1,180 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/user/domain/presentation/dialogs/override_profile_dialog.dart';
import 'package:weblibre/features/user/domain/services/user_backup.dart';
import 'package:weblibre/utils/form_validators.dart';
import 'package:weblibre/utils/ui_helper.dart';
enum RestoreTarget { createOrOverride, createNew }
class ProfileRestoreScreen extends HookConsumerWidget {
final Uri backupFileUri;
const ProfileRestoreScreen({super.key, required this.backupFileUri});
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final passwordTextController = useTextEditingController();
final nameTextController = useTextEditingController();
final restoreFuture = useState<Future<bool>?>(null);
final restoreState = useFuture(restoreFuture.value);
final restoreTarget = useState(RestoreTarget.createNew);
useEffect(() {
if (restoreState.hasError) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showErrorMessage(context, restoreState.error!.toString());
});
} else if (restoreState.hasData) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showInfoMessage(context, 'Backup restored successfully');
ProfileListRoute().go(context);
});
}
return null;
}, [restoreState.hasError, restoreState.hasData, restoreState.error]);
final disableInteraction =
restoreState.connectionState == ConnectionState.waiting;
return Scaffold(
appBar: AppBar(title: const Text('Restore Backup')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Form(
key: formKey,
child: ListView(
children: [
TextFormField(
controller: passwordTextController,
enabled: !disableInteraction,
enableSuggestions: false,
autocorrect: false,
enableIMEPersonalizedLearning: false,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
validator: (value) {
return validateRequired(
value,
message: 'Password required',
);
},
),
const SizedBox(height: 16),
RadioGroup(
groupValue: restoreTarget.value,
onChanged: (value) {
if (value != null) {
restoreTarget.value = value;
}
},
child: Column(
children: [
RadioListTile(
enabled: !disableInteraction,
value: RestoreTarget.createNew,
title: const Text('Create New User'),
subtitle: const Text('Restore backup as a new user'),
),
RadioListTile(
enabled: !disableInteraction,
value: RestoreTarget.createOrOverride,
title: const Text('Restore & Replace'),
subtitle: const Text(
'Restore backup and overwrite existing user if present',
),
),
],
),
),
if (restoreTarget.value == RestoreTarget.createNew)
TextFormField(
controller: nameTextController,
enabled: !disableInteraction,
decoration: const InputDecoration(
label: Text('Name'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
validator: validateProfileName,
),
const SizedBox(height: 16),
if (disableInteraction)
const Column(
children: [
LinearProgressIndicator(),
Text('Restoring Backup'),
],
)
else
FilledButton.icon(
icon: const Icon(MdiIcons.backupRestore),
onPressed: () {
if (formKey.currentState?.validate() ?? false) {
restoreFuture.value = switch (restoreTarget.value) {
RestoreTarget.createOrOverride =>
ref
.read(userBackupServiceProvider.notifier)
.restoreAndCreateOrOverride(
backupFileUri,
password: passwordTextController.text,
confirmOverrideCallback: () {
if (context.mounted) {
return showOverrideProfileDialog(context);
} else {
throw Exception('Override failed');
}
},
),
RestoreTarget.createNew =>
ref
.read(userBackupServiceProvider.notifier)
.restoreAndCreateNew(
backupFileUri,
profileName: nameTextController.text,
password: passwordTextController.text,
),
};
}
},
label: const Text('Restore'),
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/user/domain/presentation/dialogs/switch_profile_dialog.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/utils/exit_app.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
/// Handles the profile switching flow with confirmation dialog.
///
/// This function:
/// - Checks if the profile is already active
/// - Shows a confirmation dialog with browser restart warning
/// - Switches to the selected profile and exits the app
Future<void> handleSwitchProfile(
BuildContext context,
WidgetRef ref,
Profile profile,
) async {
final isSelected = filesystem.selectedProfile == profile.uuidValue;
// Don't allow switching to the already active profile
if (isSelected) {
if (context.mounted) {
ui_helper.showInfoMessage(context, 'This profile is already active');
}
return;
}
if (!context.mounted) return;
final shouldSwitch = await showSwitchProfileDialog(
context,
profileName: profile.name,
);
if (shouldSwitch == true) {
await ref
.read(profileRepositoryProvider.notifier)
.switchProfile(profile.id);
await exitApp(ref.container);
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
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:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/user/domain/providers/profile_auth.dart';
import 'package:weblibre/presentation/hooks/on_initialization.dart';
class LockScreen extends HookConsumerWidget {
const LockScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isAuthenticating = useState(false);
final didAutoAuthenticate = useRef(false);
Future<void> authenticate() async {
if (isAuthenticating.value) return;
isAuthenticating.value = true;
try {
await ref.read(profileAuthStateProvider.notifier).authenticate();
} finally {
if (context.mounted) {
isAuthenticating.value = false;
}
}
}
useOnInitialization(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!didAutoAuthenticate.value) {
didAutoAuthenticate.value = true;
unawaited(authenticate());
}
});
return null;
});
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(MdiIcons.lock, size: 64),
const SizedBox(height: 16),
const Text('Profile is locked'),
const SizedBox(height: 16),
FilledButton.icon(
style: FilledButton.styleFrom(minimumSize: const Size(160, 40)),
icon: const Icon(MdiIcons.fingerprint),
label: Text(isAuthenticating.value ? 'Unlocking...' : 'Unlock'),
onPressed: isAuthenticating.value ? null : authenticate,
),
],
),
),
),
);
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:exceptions/exceptions.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:saf_util/saf_util_platform_interface.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/user/data/providers.dart';
import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart';
import 'package:weblibre/features/user/domain/providers/backup_directory.dart';
import 'package:weblibre/features/user/domain/repositories/engine_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/features/user/domain/services/fingerprinting.dart';
import 'package:weblibre/features/user/domain/services/user_backup.dart';
part 'providers.g.dart';
@Riverpod()
Stream<double> iconCacheSizeMegabytes(Ref ref) {
final repository = ref.watch(userDatabaseProvider);
return repository.cacheDao.getIconCacheSize().watchSingle();
}
@Riverpod()
bool incognitoModeEnabled(Ref ref) {
return ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.deleteBrowsingDataOnQuit != null,
),
);
}
@Riverpod()
Future<Result<FingerprintOverrides>> fingerprintOverrideSettings(
Ref ref,
) async {
final fingerprintTargets = await ref.watch(fingerprintTargetsProvider.future);
final fingerprintTargetSet = fingerprintTargets.map((e) => e.name).toSet();
final overrides = ref.watch(
engineSettingsWithDefaultsProvider.select(
(settings) =>
settings.fingerprintingProtectionOverrides.mapNotNull(
(settings) =>
FingerprintOverrides.parse(settings, fingerprintTargetSet),
) ??
Result.success(FingerprintOverrides.defaults()),
),
);
return overrides;
}
@Riverpod(keepAlive: true)
Future<Profile> selectedProfile(Ref ref) async {
final profiles = await ref.watch(profileRepositoryProvider.future);
return profiles.firstWhere((p) => p.uuidValue == filesystem.selectedProfile);
}
@Riverpod()
Future<List<SafDocumentFile>> backupList(Ref ref) async {
final dirUri = ref.watch(backupDirectoryUriProvider);
if (dirUri == null) return [];
return ref.watch(userBackupServiceProvider.notifier).getBackupList(dirUri);
}
@@ -0,0 +1,203 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(iconCacheSizeMegabytes)
final iconCacheSizeMegabytesProvider = IconCacheSizeMegabytesProvider._();
final class IconCacheSizeMegabytesProvider
extends $FunctionalProvider<AsyncValue<double>, double, Stream<double>>
with $FutureModifier<double>, $StreamProvider<double> {
IconCacheSizeMegabytesProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'iconCacheSizeMegabytesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$iconCacheSizeMegabytesHash();
@$internal
@override
$StreamProviderElement<double> $createElement($ProviderPointer pointer) =>
$StreamProviderElement(pointer);
@override
Stream<double> create(Ref ref) {
return iconCacheSizeMegabytes(ref);
}
}
String _$iconCacheSizeMegabytesHash() =>
r'5d7f5f6485060b08ce4fd8fa634f07bf8bfdbd2d';
@ProviderFor(incognitoModeEnabled)
final incognitoModeEnabledProvider = IncognitoModeEnabledProvider._();
final class IncognitoModeEnabledProvider
extends $FunctionalProvider<bool, bool, bool>
with $Provider<bool> {
IncognitoModeEnabledProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'incognitoModeEnabledProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$incognitoModeEnabledHash();
@$internal
@override
$ProviderElement<bool> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
bool create(Ref ref) {
return incognitoModeEnabled(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$incognitoModeEnabledHash() =>
r'36957b70a5261f9d3ad228e07cc8dd5c8f616082';
@ProviderFor(fingerprintOverrideSettings)
final fingerprintOverrideSettingsProvider =
FingerprintOverrideSettingsProvider._();
final class FingerprintOverrideSettingsProvider
extends
$FunctionalProvider<
AsyncValue<Result<FingerprintOverrides>>,
Result<FingerprintOverrides>,
FutureOr<Result<FingerprintOverrides>>
>
with
$FutureModifier<Result<FingerprintOverrides>>,
$FutureProvider<Result<FingerprintOverrides>> {
FingerprintOverrideSettingsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'fingerprintOverrideSettingsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$fingerprintOverrideSettingsHash();
@$internal
@override
$FutureProviderElement<Result<FingerprintOverrides>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<Result<FingerprintOverrides>> create(Ref ref) {
return fingerprintOverrideSettings(ref);
}
}
String _$fingerprintOverrideSettingsHash() =>
r'd4d40ec425098fb1f5a2f0c4944f058829a41a0a';
@ProviderFor(selectedProfile)
final selectedProfileProvider = SelectedProfileProvider._();
final class SelectedProfileProvider
extends $FunctionalProvider<AsyncValue<Profile>, Profile, FutureOr<Profile>>
with $FutureModifier<Profile>, $FutureProvider<Profile> {
SelectedProfileProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedProfileProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedProfileHash();
@$internal
@override
$FutureProviderElement<Profile> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<Profile> create(Ref ref) {
return selectedProfile(ref);
}
}
String _$selectedProfileHash() => r'c703cad8f30abb4f5f42db0119756ee6791ac477';
@ProviderFor(backupList)
final backupListProvider = BackupListProvider._();
final class BackupListProvider
extends
$FunctionalProvider<
AsyncValue<List<SafDocumentFile>>,
List<SafDocumentFile>,
FutureOr<List<SafDocumentFile>>
>
with
$FutureModifier<List<SafDocumentFile>>,
$FutureProvider<List<SafDocumentFile>> {
BackupListProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'backupListProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$backupListHash();
@$internal
@override
$FutureProviderElement<List<SafDocumentFile>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<SafDocumentFile>> create(Ref ref) {
return backupList(ref);
}
}
String _$backupListHash() => r'527bfdab7b537b08764ff77dd69de14d38846d41';
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:riverpod/experimental/persist.dart';
import 'package:riverpod_annotation/experimental/persist.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/providers.dart';
part 'backup_directory.g.dart';
@Riverpod(keepAlive: true)
class BackupDirectoryUri extends _$BackupDirectoryUri {
// ignore: use_setters_to_change_properties
void set(Uri? value) => state = value;
@override
Uri? build() {
persist(
ref.watch(riverpodDatabaseStorageProvider),
key: 'BackupDirectoryUri',
encode: (state) => state?.toString() ?? '',
decode: (encoded) => encoded.isEmpty ? null : Uri.parse(encoded),
);
return stateOrNull;
}
}
@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backup_directory.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(BackupDirectoryUri)
final backupDirectoryUriProvider = BackupDirectoryUriProvider._();
final class BackupDirectoryUriProvider
extends $NotifierProvider<BackupDirectoryUri, Uri?> {
BackupDirectoryUriProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'backupDirectoryUriProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$backupDirectoryUriHash();
@$internal
@override
BackupDirectoryUri create() => BackupDirectoryUri();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Uri? value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Uri?>(value),
);
}
}
String _$backupDirectoryUriHash() =>
r'4e5f4e7bde90b2a92c559afe8774ed5ee503277d';
abstract class _$BackupDirectoryUri extends $Notifier<Uri?> {
Uri? build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<Uri?, Uri?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<Uri?, Uri?>,
Uri?,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/foundation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/domain/providers.dart';
import 'package:weblibre/features/user/domain/services/local_authentication.dart';
part 'profile_auth.g.dart';
String profileAccessAuthKey(String profileId) => 'profile_access::$profileId';
@Riverpod(keepAlive: true)
class ProfileAuthState extends _$ProfileAuthState {
bool _bootstrapped = false;
Future<void> bootstrapFromProfile() async {
if (_bootstrapped) return;
final profile = await ref.read(selectedProfileProvider.future);
if (!ref.mounted) return;
_bootstrapped = true;
if (!profile.authSettings.authenticationRequired) {
_unlock();
}
}
Future<bool> authenticate() async {
final profile = await ref.read(selectedProfileProvider.future);
if (!ref.mounted) return false;
if (!profile.authSettings.authenticationRequired) {
_unlock();
return true;
}
final result = await ref
.read(localAuthenticationServiceProvider.notifier)
.authenticate(
authKey: profileAccessAuthKey(profile.id),
localizedReason: 'Unlock profile',
settings: profile.authSettings,
useAuthCache: true,
);
if (!ref.mounted) return false;
state = result;
return result;
}
Future<void> revalidateAfterResume() async {
if (!state) return;
final profile = await ref.read(selectedProfileProvider.future);
if (!ref.mounted || !profile.authSettings.authenticationRequired) return;
final cached = ref
.read(localAuthenticationServiceProvider.notifier)
.isCached(profileAccessAuthKey(profile.id));
if (!cached && ref.mounted) {
_lock();
}
}
void _lock() {
state = false;
}
void _unlock() {
state = true;
}
@override
bool build() {
return false;
}
}
@Riverpod(keepAlive: true)
Raw<ProfileAuthNotifier> profileAuthNotifier(Ref ref) {
final notifier = ProfileAuthNotifier();
ref.listen<bool>(profileAuthStateProvider, (_, _) {
notifier.notify();
});
ref.onDispose(notifier.dispose);
return notifier;
}
class ProfileAuthNotifier extends ChangeNotifier {
void notify() => notifyListeners();
}
@@ -0,0 +1,110 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'profile_auth.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(ProfileAuthState)
final profileAuthStateProvider = ProfileAuthStateProvider._();
final class ProfileAuthStateProvider
extends $NotifierProvider<ProfileAuthState, bool> {
ProfileAuthStateProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'profileAuthStateProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$profileAuthStateHash();
@$internal
@override
ProfileAuthState create() => ProfileAuthState();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$profileAuthStateHash() => r'9eb65fdb76baa0b088fc12a8063ea4ee63d54ac4';
abstract class _$ProfileAuthState extends $Notifier<bool> {
bool build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<bool, bool>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<bool, bool>,
bool,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@ProviderFor(profileAuthNotifier)
final profileAuthProvider = ProfileAuthNotifierProvider._();
final class ProfileAuthNotifierProvider
extends
$FunctionalProvider<
Raw<ProfileAuthNotifier>,
Raw<ProfileAuthNotifier>,
Raw<ProfileAuthNotifier>
>
with $Provider<Raw<ProfileAuthNotifier>> {
ProfileAuthNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'profileAuthProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$profileAuthNotifierHash();
@$internal
@override
$ProviderElement<Raw<ProfileAuthNotifier>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
Raw<ProfileAuthNotifier> create(Ref ref) {
return profileAuthNotifier(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Raw<ProfileAuthNotifier> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Raw<ProfileAuthNotifier>>(value),
);
}
}
String _$profileAuthNotifierHash() =>
r'795f47b1494e4a9cdd74f5ff22d431b2bc59ffbd';
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:typed_data';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
import 'package:weblibre/features/user/data/providers.dart';
part 'cache.g.dart';
@Riverpod(keepAlive: true)
class CacheRepository extends _$CacheRepository {
Future<void> clearCache() {
return ref.read(userDatabaseProvider).cacheDao.clearIconCache();
}
Future<void> cacheIcon(Uri url, Uint8List bytes) {
return ref.read(userDatabaseProvider).cacheDao.cacheIcon(url.origin, bytes);
}
Future<Uint8List?> getCachedIcon(String origin) {
return ref
.read(userDatabaseProvider)
.cacheDao
.getCachedIcon(origin)
.getSingleOrNull();
}
@override
void build() {
final eventService = ref.watch(eventServiceProvider);
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);
}
},
onError: (Object error, StackTrace stackTrace) {
logger.e(
'Error in icon update events',
error: error,
stackTrace: stackTrace,
);
},
);
ref.onDispose(() async {
await sub.cancel();
});
}
}
@@ -0,0 +1,62 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'cache.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(CacheRepository)
final cacheRepositoryProvider = CacheRepositoryProvider._();
final class CacheRepositoryProvider
extends $NotifierProvider<CacheRepository, void> {
CacheRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'cacheRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$cacheRepositoryHash();
@$internal
@override
CacheRepository create() => CacheRepository();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
}
String _$cacheRepositoryHash() => r'e3cd7461aefe9e034a663169cd81ab7f69c2640e';
abstract class _$CacheRepository extends $Notifier<void> {
void build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,283 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart';
import 'package:weblibre/features/user/data/providers.dart';
part 'engine_settings.g.dart';
typedef UpdateEngineSettingsFunc =
EngineSettings Function(EngineSettings currentSettings);
@Riverpod(keepAlive: true)
class EngineSettingsRepository extends _$EngineSettingsRepository {
final _partitionKey = 'engine';
EngineSettings _deserializeSettings(
List<MapEntry<String, DriftAny?>> entries,
) {
final db = ref.read(userDatabaseProvider);
final settings = Map.fromEntries(entries);
return 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),
'userAgent': settings['userAgent']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'queryParameterStripping': settings['queryParameterStripping']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'bounceTrackingProtectionMode': settings['bounceTrackingProtectionMode']
?.readAs(DriftSqlType.string, db.typeMapping),
'enterpriseRootsEnabled': settings['enterpriseRootsEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'addonCollection': settings['addonCollection']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'dohSettingsMode': settings['dohSettingsMode']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'dohProviderUrl': settings['dohProviderUrl']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'dohDefaultProviderUrl': settings['dohDefaultProviderUrl']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'dohExceptionsList': settings['dohExceptionsList']
?.readAs(DriftSqlType.string, db.typeMapping)
.mapNotNull(jsonDecode),
'fingerprintingProtectionOverrides':
settings['fingerprintingProtectionOverrides']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'enablePdfJs': settings['enablePdfJs']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'locales': settings['locales']
?.readAs(DriftSqlType.string, db.typeMapping)
.mapNotNull(jsonDecode),
// Custom Tracking Protection
'blockCookies': settings['blockCookies']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'customCookiePolicy': settings['customCookiePolicy']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'blockTrackingContent': settings['blockTrackingContent']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'trackingContentScope': settings['trackingContentScope']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'blockCryptominers': settings['blockCryptominers']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'blockFingerprinters': settings['blockFingerprinters']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'blockRedirectTrackers': settings['blockRedirectTrackers']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'blockSuspectedFingerprinters': settings['blockSuspectedFingerprinters']
?.readAs(DriftSqlType.bool, db.typeMapping),
'suspectedFingerprintersScope': settings['suspectedFingerprintersScope']
?.readAs(DriftSqlType.string, db.typeMapping),
'allowListBaseline': settings['allowListBaseline']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'allowListConvenience': settings['allowListConvenience']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
// Web Content Settings
'webFontsEnabled': settings['webFontsEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'automaticFontSizeAdjustment': settings['automaticFontSizeAdjustment']
?.readAs(DriftSqlType.bool, db.typeMapping),
'fontSizeFactor': settings['fontSizeFactor']?.readAs(
DriftSqlType.double,
db.typeMapping,
),
'fontInflationEnabled': settings['fontInflationEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'displayDensityOverride': settings['displayDensityOverride']?.readAs(
DriftSqlType.double,
db.typeMapping,
),
'screenWidthOverride': settings['screenWidthOverride']?.readAs(
DriftSqlType.int,
db.typeMapping,
),
'screenHeightOverride': settings['screenHeightOverride']?.readAs(
DriftSqlType.int,
db.typeMapping,
),
'inputAutoZoomEnabled': settings['inputAutoZoomEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
// Process Isolation Settings
'fissionEnabled': settings['fissionEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'isolatedProcessEnabled': settings['isolatedProcessEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'appZygoteProcessEnabled': settings['appZygoteProcessEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'extensionsWebAPIEnabled': settings['extensionsWebAPIEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
// LNA Settings
'lnaBlocking': settings['lnaBlocking']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'lnaBlockTrackers': settings['lnaBlockTrackers']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'lnaEnabled': settings['lnaEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
});
}
Future<void> updateSettings(
UpdateEngineSettingsFunc updateWithCurrent,
) async {
final db = ref.read(userDatabaseProvider);
final current = await fetchSettings();
final oldJson = current.toJson();
final newJson = updateWithCurrent(current).toJson();
return db.transaction(() async {
for (final MapEntry(:key, :value) in newJson.entries) {
if (oldJson[key] != value) {
await db.settingDao.updateSetting(key, _partitionKey, value);
}
}
});
}
Future<EngineSettings> fetchSettings() {
return ref
.read(userDatabaseProvider)
.settingDao
.getAllSettingsOfPartitionKey(_partitionKey)
.get()
.then(_deserializeSettings);
}
@override
Stream<EngineSettings> build() {
final db = ref.watch(userDatabaseProvider);
return db.settingDao
.getAllSettingsOfPartitionKey(_partitionKey)
.watch()
.map((entries) {
return _deserializeSettings(entries);
});
}
}
@Riverpod()
EngineSettings engineSettingsWithDefaults(Ref ref) {
return ref.watch(
engineSettingsRepositoryProvider.select(
(value) => value.value ?? EngineSettings.withDefaults(),
),
);
}
@@ -0,0 +1,99 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'engine_settings.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(EngineSettingsRepository)
final engineSettingsRepositoryProvider = EngineSettingsRepositoryProvider._();
final class EngineSettingsRepositoryProvider
extends $StreamNotifierProvider<EngineSettingsRepository, EngineSettings> {
EngineSettingsRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'engineSettingsRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$engineSettingsRepositoryHash();
@$internal
@override
EngineSettingsRepository create() => EngineSettingsRepository();
}
String _$engineSettingsRepositoryHash() =>
r'4abe41cfeba8e39484683033f11c54be644fa2b2';
abstract class _$EngineSettingsRepository
extends $StreamNotifier<EngineSettings> {
Stream<EngineSettings> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<EngineSettings>, EngineSettings>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<EngineSettings>, EngineSettings>,
AsyncValue<EngineSettings>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@ProviderFor(engineSettingsWithDefaults)
final engineSettingsWithDefaultsProvider =
EngineSettingsWithDefaultsProvider._();
final class EngineSettingsWithDefaultsProvider
extends $FunctionalProvider<EngineSettings, EngineSettings, EngineSettings>
with $Provider<EngineSettings> {
EngineSettingsWithDefaultsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'engineSettingsWithDefaultsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$engineSettingsWithDefaultsHash();
@$internal
@override
$ProviderElement<EngineSettings> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
EngineSettings create(Ref ref) {
return engineSettingsWithDefaults(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(EngineSettings value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<EngineSettings>(value),
);
}
}
String _$engineSettingsWithDefaultsHash() =>
r'd47fa79c0ad87a2357de58133585b4f6b097b068';
@@ -0,0 +1,286 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/data/providers.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,
),
'uiScaleFactor': settings['uiScaleFactor']?.readAs(
DriftSqlType.double,
db.typeMapping,
),
'disableAnimations': settings['disableAnimations']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'showModalBarrier': settings['showModalBarrier']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'enableReadability': settings['enableReadability']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'enforceReadability': settings['enforceReadability']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'deleteBrowsingDataOnQuit': settings['deleteBrowsingDataOnQuit']
?.readAs(DriftSqlType.string, db.typeMapping)
.mapNotNull(jsonDecode),
'defaultSearchProvider': settings['defaultSearchProvider']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'defaultSearchSuggestionsProvider':
settings['defaultSearchSuggestionsProvider']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'createChildTabsOption': settings['createChildTabsOption']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'enableLocalAiFeatures': settings['enableLocalAiFeatures']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'showContainerUi': settings['showContainerUi']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'showIsolatedTabUi': settings['showIsolatedTabUi']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'defaultCreateTabType': settings['defaultCreateTabType']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'newTabPosition': settings['newTabPosition']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'tabIntentOpenSetting': settings['tabIntentOpenSetting']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'autoHideTabBar': settings['autoHideTabBar']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'tabBarSwipeAction': settings['tabBarSwipeAction']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'historyAutoCleanInterval': settings['historyAutoCleanInterval']?.readAs(
DriftSqlType.int,
db.typeMapping,
),
'tabViewBottomSheet': settings['tabViewBottomSheet']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'tabBarShowContextualBar': settings['tabBarShowContextualBar']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'tabBarShowQuickTabSwitcherBar': settings['tabBarShowQuickTabSwitcherBar']
?.readAs(DriftSqlType.bool, db.typeMapping),
'tabBarPosition': settings['tabBarPosition']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'tabBarLayout': settings['tabBarLayout']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'quickTabSwitcherMode': settings['quickTabSwitcherMode']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'pullToRefreshEnabled': settings['pullToRefreshEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'useExternalDownloadManager': settings['useExternalDownloadManager']
?.readAs(DriftSqlType.bool, db.typeMapping),
'doubleBackCloseTab': settings['doubleBackCloseTab']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'unassignedTabsAutoCleanInterval':
settings['unassignedTabsAutoCleanInterval']?.readAs(
DriftSqlType.int,
db.typeMapping,
),
'maxSearchHistoryEntries': settings['maxSearchHistoryEntries']?.readAs(
DriftSqlType.int,
db.typeMapping,
),
'allowClipboardAccess': settings['allowClipboardAccess']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'tabListShowFavicons': settings['tabListShowFavicons']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'quickTabSwitcherShowTitles': settings['quickTabSwitcherShowTitles']
?.readAs(DriftSqlType.bool, db.typeMapping),
'quickTabSwitcherShowHistorySuggestions':
settings['quickTabSwitcherShowHistorySuggestions']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'syncServerOverride': settings['syncServerOverride']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'syncTokenServerOverride': settings['syncTokenServerOverride']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'urlCleanerEnabled': settings['urlCleanerEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'urlCleanerAutoApply': settings['urlCleanerAutoApply']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'urlCleanerAllowReferralMarketing':
settings['urlCleanerAllowReferralMarketing']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'urlCleanerCatalogUrl': settings['urlCleanerCatalogUrl']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'urlCleanerHashUrl': settings['urlCleanerHashUrl']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'urlCleanerAutoUpdate': settings['urlCleanerAutoUpdate']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'urlCleanerLastCheckEpochMs': settings['urlCleanerLastCheckEpochMs']
?.readAs(DriftSqlType.int, db.typeMapping),
'urlCleanerLastUpdateWasAuto': settings['urlCleanerLastUpdateWasAuto']
?.readAs(DriftSqlType.bool, db.typeMapping),
'smallWebTabType': settings['smallWebTabType']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'tabBarLongPressUrlCopy': settings['tabBarLongPressUrlCopy']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'unshortenerEnabled': settings['unshortenerEnabled']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'unshortenerToken': settings['unshortenerToken']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'allowNonManifestPwaInstall': settings['allowNonManifestPwaInstall']
?.readAs(DriftSqlType.bool, db.typeMapping),
});
}
//Eager fetch, when up to date settings are required
Future<GeneralSettings> fetchSettings() {
return ref
.read(userDatabaseProvider)
.settingDao
.getAllSettingsOfPartitionKey(_partitionKey)
.get()
.then(_deserializeSettings);
}
Future<void> updateSettings(
UpdateGeneralSettingsFunc updateWithCurrent,
) async {
final db = ref.read(userDatabaseProvider);
final current = await fetchSettings();
final oldJson = current.toJson();
final newJson = updateWithCurrent(current).toJson();
return db.transaction(() async {
for (final MapEntry(:key, :value) in newJson.entries) {
if (oldJson[key] != value) {
await db.settingDao.updateSetting(key, _partitionKey, value);
}
}
});
}
@override
Stream<GeneralSettings> build() {
final db = ref.watch(userDatabaseProvider);
return db.settingDao
.getAllSettingsOfPartitionKey(_partitionKey)
.watch()
.map((event) {
return _deserializeSettings(event);
});
}
}
@Riverpod(keepAlive: true)
GeneralSettings generalSettingsWithDefaults(Ref ref) {
return ref.watch(
generalSettingsRepositoryProvider.select(
(value) => value.value ?? GeneralSettings.withDefaults(),
),
);
}
@@ -0,0 +1,101 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'general_settings.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(GeneralSettingsRepository)
final generalSettingsRepositoryProvider = GeneralSettingsRepositoryProvider._();
final class GeneralSettingsRepositoryProvider
extends
$StreamNotifierProvider<GeneralSettingsRepository, GeneralSettings> {
GeneralSettingsRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'generalSettingsRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$generalSettingsRepositoryHash();
@$internal
@override
GeneralSettingsRepository create() => GeneralSettingsRepository();
}
String _$generalSettingsRepositoryHash() =>
r'afc63f4d929ea146f0b8a7c0f6936b06c5a41024';
abstract class _$GeneralSettingsRepository
extends $StreamNotifier<GeneralSettings> {
Stream<GeneralSettings> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<GeneralSettings>, GeneralSettings>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<GeneralSettings>, GeneralSettings>,
AsyncValue<GeneralSettings>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@ProviderFor(generalSettingsWithDefaults)
final generalSettingsWithDefaultsProvider =
GeneralSettingsWithDefaultsProvider._();
final class GeneralSettingsWithDefaultsProvider
extends
$FunctionalProvider<GeneralSettings, GeneralSettings, GeneralSettings>
with $Provider<GeneralSettings> {
GeneralSettingsWithDefaultsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'generalSettingsWithDefaultsProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$generalSettingsWithDefaultsHash();
@$internal
@override
$ProviderElement<GeneralSettings> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
GeneralSettings create(Ref ref) {
return generalSettingsWithDefaults(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(GeneralSettings value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<GeneralSettings>(value),
);
}
}
String _$generalSettingsWithDefaultsHash() =>
r'9da4a00a3500286fbf515ee319fa911bfacab40e';
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/providers.dart';
part 'onboarding.g.dart';
@Riverpod(keepAlive: true)
class OnboardingRepository extends _$OnboardingRepository {
static const targetRevision = 3;
Future<int?> getCurrentRevision() {
return ref
.read(userDatabaseProvider)
.onboardingDao
.getLastRevision()
.getSingleOrNull();
}
Future<void> pushRevision(int revision) {
return ref
.read(userDatabaseProvider)
.onboardingDao
.pushRevision(revision, DateTime.now());
}
Future<bool> isOutdated() async {
final current = await getCurrentRevision();
return current == null || current < targetRevision;
}
@override
void build() {
return;
}
}
@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'onboarding.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(OnboardingRepository)
final onboardingRepositoryProvider = OnboardingRepositoryProvider._();
final class OnboardingRepositoryProvider
extends $NotifierProvider<OnboardingRepository, void> {
OnboardingRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'onboardingRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$onboardingRepositoryHash();
@$internal
@override
OnboardingRepository create() => OnboardingRepository();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
}
String _$onboardingRepositoryHash() =>
r'5d583af3ae38b351357b16809ef32bb6807f5c05';
abstract class _$OnboardingRepository extends $Notifier<void> {
void build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:uuid/uuid.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/user/data/models/auth_settings.dart';
part 'profile.g.dart';
@Riverpod(keepAlive: true)
class ProfileRepository extends _$ProfileRepository {
Future<List<Profile>> _readProfiles() {
return filesystem.getAvailableProfileDirectories().then((dirs) async {
final profiles = await Future.wait(
dirs.map(filesystem.readProfileMetadata),
);
return profiles.nonNulls.toList();
});
}
Future<void> switchProfile(String id) async {
await filesystem.setStartupProfile(UuidValue.withValidation(id));
}
Future<Profile> createProfile({
required String name,
AuthSettings? authSettings,
}) async {
final profile = Profile.create(name: name, authSettings: authSettings);
if (!await filesystem.createNewProfile(profile)) {
throw Exception('Could not create profile');
}
ref.invalidateSelf();
return profile;
}
Future<void> updateProfileMetadata(Profile profile) async {
await filesystem.updateProfileMetadata(profile);
ref.invalidateSelf();
}
Future<bool> deleteProfile(String id) async {
final uuid = UuidValue.withValidation(id);
if (filesystem.selectedProfile == uuid) {
return false;
}
await filesystem.getProfileDir(uuid).delete(recursive: true);
ref.invalidateSelf();
return true;
}
@override
Future<List<Profile>> build() {
return _readProfiles();
}
}
@@ -0,0 +1,54 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'profile.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(ProfileRepository)
final profileRepositoryProvider = ProfileRepositoryProvider._();
final class ProfileRepositoryProvider
extends $AsyncNotifierProvider<ProfileRepository, List<Profile>> {
ProfileRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'profileRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$profileRepositoryHash();
@$internal
@override
ProfileRepository create() => ProfileRepository();
}
String _$profileRepositoryHash() => r'b770e7406e1602f808cc8076c1eda67b4fce6b2d';
abstract class _$ProfileRepository extends $AsyncNotifier<List<Profile>> {
FutureOr<List<Profile>> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<List<Profile>>, List<Profile>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<List<Profile>>, List<Profile>>,
AsyncValue<List<Profile>>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:drift/drift.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/models/tor_settings.dart';
import 'package:weblibre/features/user/data/providers.dart';
part 'tor_settings.g.dart';
typedef UpdateTorSettingsFunc =
TorSettings Function(TorSettings currentSettings);
@Riverpod(keepAlive: true)
class TorSettingsRepository extends _$TorSettingsRepository {
final _partitionKey = 'tor';
TorSettings _deserializeSettings(List<MapEntry<String, DriftAny?>> entries) {
final settings = Map.fromEntries(entries);
final db = ref.read(userDatabaseProvider);
return TorSettings.fromJson({
'proxyRegularTabsMode': settings['proxyRegularTabsMode']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'proxyPrivateTabsTor': settings['proxyPrivateTabsTor']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'config': settings['config']?.readAs(DriftSqlType.string, db.typeMapping),
'requireBridge': settings['requireBridge']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'fetchRemoteBridges': settings['fetchRemoteBridges']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'entryNodeCountry': settings['entryNodeCountry']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'exitNodeCountry': settings['exitNodeCountry']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
});
}
//Eager fetch, when up to date settings are required
Future<TorSettings> fetchSettings() {
return ref
.read(userDatabaseProvider)
.settingDao
.getAllSettingsOfPartitionKey(_partitionKey)
.get()
.then(_deserializeSettings);
}
Future<void> updateSettings(UpdateTorSettingsFunc updateWithCurrent) async {
final db = ref.read(userDatabaseProvider);
final current = await fetchSettings();
final oldJson = current.toJson();
final newJson = updateWithCurrent(current).toJson();
return db.transaction(() async {
for (final MapEntry(:key, :value) in newJson.entries) {
if (oldJson[key] != value) {
await db.settingDao.updateSetting(key, _partitionKey, value);
}
}
});
}
@override
Stream<TorSettings> build() {
final db = ref.watch(userDatabaseProvider);
return db.settingDao
.getAllSettingsOfPartitionKey(_partitionKey)
.watch()
.map((event) {
return _deserializeSettings(event);
});
}
}
@Riverpod(keepAlive: true)
TorSettings torSettingsWithDefaults(Ref ref) {
return ref.watch(
torSettingsRepositoryProvider.select(
(value) => value.value ?? TorSettings.withDefaults(),
),
);
}
@@ -0,0 +1,97 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tor_settings.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(TorSettingsRepository)
final torSettingsRepositoryProvider = TorSettingsRepositoryProvider._();
final class TorSettingsRepositoryProvider
extends $StreamNotifierProvider<TorSettingsRepository, TorSettings> {
TorSettingsRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'torSettingsRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$torSettingsRepositoryHash();
@$internal
@override
TorSettingsRepository create() => TorSettingsRepository();
}
String _$torSettingsRepositoryHash() =>
r'f771f23f17903bd192b24604bab522fb20571ffd';
abstract class _$TorSettingsRepository extends $StreamNotifier<TorSettings> {
Stream<TorSettings> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<TorSettings>, TorSettings>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<TorSettings>, TorSettings>,
AsyncValue<TorSettings>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@ProviderFor(torSettingsWithDefaults)
final torSettingsWithDefaultsProvider = TorSettingsWithDefaultsProvider._();
final class TorSettingsWithDefaultsProvider
extends $FunctionalProvider<TorSettings, TorSettings, TorSettings>
with $Provider<TorSettings> {
TorSettingsWithDefaultsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'torSettingsWithDefaultsProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$torSettingsWithDefaultsHash();
@$internal
@override
$ProviderElement<TorSettings> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
TorSettings create(Ref ref) {
return torSettingsWithDefaults(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(TorSettings value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<TorSettings>(value),
);
}
}
String _$torSettingsWithDefaultsHash() =>
r'501a7ed7f14870d40b8f60303d3c385a45d9f542';
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/models/rfp_target.dart';
part 'fingerprinting.g.dart';
@Riverpod(keepAlive: true)
Future<List<RFPTarget>> fingerprintTargets(Ref ref) async {
final json =
await rootBundle
.loadString('assets/preferences/rfp_targets.json')
.then(jsonDecode)
as List<dynamic>;
return json
.map((e) => RFPTarget.fromJson(e as Map<String, dynamic>))
.toList();
}
@@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fingerprinting.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(fingerprintTargets)
final fingerprintTargetsProvider = FingerprintTargetsProvider._();
final class FingerprintTargetsProvider
extends
$FunctionalProvider<
AsyncValue<List<RFPTarget>>,
List<RFPTarget>,
FutureOr<List<RFPTarget>>
>
with $FutureModifier<List<RFPTarget>>, $FutureProvider<List<RFPTarget>> {
FingerprintTargetsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'fingerprintTargetsProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$fingerprintTargetsHash();
@$internal
@override
$FutureProviderElement<List<RFPTarget>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<RFPTarget>> create(Ref ref) {
return fingerprintTargets(ref);
}
}
String _$fingerprintTargetsHash() =>
r'1ec5933a82941b84fdad2130ccf1ba2156ddc33a';
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'package:local_auth/local_auth.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/user/data/models/auth_settings.dart';
part 'local_authentication.g.dart';
@Riverpod(keepAlive: true)
class LocalAuthenticationService extends _$LocalAuthenticationService {
final _auth = LocalAuthentication();
final _cache = <String, (DateTime, AuthSettings)>{};
void evictCacheOnBackground() {
_cache.removeWhere(
(key, value) => value.$2.autoLockMode == AutoLockMode.background,
);
}
bool isCached(String authKey) {
final auth = _cache[authKey];
if (auth == null) return false;
if (auth.$2.autoLockMode == AutoLockMode.timeout) {
return DateTime.now().difference(auth.$1) < auth.$2.timeout;
}
// Background mode cache stays valid until app background eviction.
return true;
}
Future<bool> authenticate({
required String authKey,
required String localizedReason,
AuthSettings? settings,
bool useAuthCache = false,
}) async {
try {
final useCache = useAuthCache && isCached(authKey);
final success =
useCache ||
await _auth.authenticate(localizedReason: localizedReason);
if (success && settings != null) {
_cache[authKey] = (DateTime.now(), settings);
}
return success;
} on LocalAuthException catch (e, s) {
logger.e('Could not authenticate', error: e, stackTrace: s);
return false;
}
}
@override
Future<bool> build() {
return _auth.canCheckBiometrics;
}
}
@@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'local_authentication.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(LocalAuthenticationService)
final localAuthenticationServiceProvider =
LocalAuthenticationServiceProvider._();
final class LocalAuthenticationServiceProvider
extends $AsyncNotifierProvider<LocalAuthenticationService, bool> {
LocalAuthenticationServiceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'localAuthenticationServiceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$localAuthenticationServiceHash();
@$internal
@override
LocalAuthenticationService create() => LocalAuthenticationService();
}
String _$localAuthenticationServiceHash() =>
r'0f4b2b47e94b2426a2219eca4eb2258bf683ab7c';
abstract class _$LocalAuthenticationService extends $AsyncNotifier<bool> {
FutureOr<bool> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<bool>, bool>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<bool>, bool>,
AsyncValue<bool>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -0,0 +1,392 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:convert/convert.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:saf_stream/saf_stream.dart';
import 'package:saf_util/saf_util.dart';
import 'package:saf_util/saf_util_platform_interface.dart';
import 'package:secure_archive/secure_archive.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/domain/entities/profile.dart';
import 'package:weblibre/features/user/domain/providers/backup_directory.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
part 'user_backup.g.dart';
@Riverpod(keepAlive: true)
class UserBackupService extends _$UserBackupService {
static final dateFormatter = FixedDateTimeFormatter('YYYY-MM-DD_hhmmss');
static const _excludedBackupRelativePaths = {'cache'};
static final _safUtil = SafUtil();
static final _safStream = SafStream();
bool _isExcludedBackupPath(String relativePath) {
final normalizedPath = p.normalize(relativePath);
for (final excludedPath in _excludedBackupRelativePaths) {
if (normalizedPath == excludedPath ||
p.isWithin(excludedPath, normalizedPath)) {
return true;
}
}
return false;
}
Future<void> _copyCuratedBackupSource(
Directory rootDirectory,
Directory sourceDirectory,
Directory targetDirectory,
) async {
await targetDirectory.create(recursive: true);
await for (final entity in sourceDirectory.list(followLinks: false)) {
final relativePath = p.relative(entity.path, from: rootDirectory.path);
if (_isExcludedBackupPath(relativePath)) {
continue;
}
final targetPath = p.join(targetDirectory.path, p.basename(entity.path));
if (entity is Directory) {
await _copyCuratedBackupSource(
rootDirectory,
entity,
Directory(targetPath),
);
} else if (entity is File) {
await entity.copy(targetPath);
} else if (entity is Link) {
await Link(targetPath).create(await entity.target());
}
}
}
Future<Directory> _prepareBackupSourceDirectory(
Directory sourceDirectory, {
required bool skipCaches,
}) async {
if (!skipCaches) {
return sourceDirectory;
}
final tempDirectory = await getTemporaryDirectory();
final curatedDirectory = Directory(
p.join(
tempDirectory.path,
'backup_source_${DateTime.now().microsecondsSinceEpoch}',
),
);
try {
await _copyCuratedBackupSource(
sourceDirectory,
sourceDirectory,
curatedDirectory,
);
return curatedDirectory;
} catch (_) {
try {
if (await curatedDirectory.exists()) {
await curatedDirectory.delete(recursive: true);
}
} catch (_) {
// Ignore cleanup errors for partially copied backup sources.
}
rethrow;
}
}
Uri _requireBackupDirectoryUri() {
final uri = ref.read(backupDirectoryUriProvider);
if (uri == null) {
throw Exception('No backup directory configured');
}
return uri;
}
Future<List<SafDocumentFile>> getBackupList(Uri dirUri) async {
final files = await _safUtil.list(dirUri.toString());
return files
.where((f) => !f.isDir && f.name.endsWith('.weblibre'))
.toList();
}
Future<bool> createUserBackup(
Profile profile, {
required String password,
required bool integrityCheck,
required bool skipCaches,
}) async {
final dirUri = _requireBackupDirectoryUri();
final timestamp = dateFormatter.encode(DateTime.now());
final fileName = 'backup_${profile.name}_$timestamp.weblibre';
final sourceDirectory = filesystem.getProfileDir(profile.uuidValue);
final tempDir = await getTemporaryDirectory();
final tempFile = File(p.join(tempDir.path, fileName));
Directory? curatedSourceDirectory;
try {
curatedSourceDirectory = await _prepareBackupSourceDirectory(
sourceDirectory,
skipCaches: skipCaches,
);
final backup = SecureArchivePack(
outputFile: tempFile,
sourceDirectory: curatedSourceDirectory,
argon2Params: Argon2Params.memoryConstrained(),
);
await backup.pack(password, integrityCheck: integrityCheck);
await _safStream.pasteLocalFile(
tempFile.path,
dirUri.toString(),
fileName,
'application/octet-stream',
);
return true;
} finally {
try {
if (await tempFile.exists()) {
await tempFile.delete();
}
} catch (e, s) {
logger.w(
'Failed to cleanup temporary backup file: ${tempFile.path}',
error: e,
stackTrace: s,
);
}
if (curatedSourceDirectory != null &&
curatedSourceDirectory.path != sourceDirectory.path) {
try {
if (await curatedSourceDirectory.exists()) {
await curatedSourceDirectory.delete(recursive: true);
}
} catch (e, s) {
logger.w(
'Failed to cleanup curated backup directory: ${curatedSourceDirectory.path}',
error: e,
stackTrace: s,
);
}
}
}
}
Future<bool> restoreAndCreateNew(
Uri backupFileUri, {
required String profileName,
required String password,
}) async {
final tempDir = await getTemporaryDirectory();
final tempFile = File(p.join(tempDir.path, 'restore_temp.weblibre'));
final outputDirectory = Directory(
p.join(filesystem.profilesDir.path, 'restore_temp'),
);
try {
await _safStream.copyToLocalFile(backupFileUri.toString(), tempFile.path);
final backup = SecureArchiveUnpack(
inputFile: tempFile,
outputDirectory: outputDirectory,
argon2Params: Argon2Params.memoryConstrained(),
);
await backup.unpack(password).then((_) async {
final newProfile = Profile.create(name: profileName);
final newPath = filesystem.getProfileDir(newProfile.uuidValue);
await outputDirectory.rename(newPath.path);
await filesystem.updateProfileMetadata(newProfile);
await filesystem.healProfile(newPath);
});
ref.invalidate(profileRepositoryProvider);
return true;
} finally {
try {
if (await tempFile.exists()) {
await tempFile.delete();
}
} catch (e, s) {
logger.w(
'Failed to cleanup temporary restore file: ${tempFile.path}',
error: e,
stackTrace: s,
);
}
try {
if (await outputDirectory.exists()) {
await outputDirectory.delete(recursive: true);
}
} catch (e, s) {
logger.w(
'Failed to cleanup temporary backup directory: ${outputDirectory.path}',
error: e,
stackTrace: s,
);
}
}
}
Future<bool> restoreAndCreateOrOverride(
Uri backupFileUri, {
required String password,
required FutureOr<bool?> Function() confirmOverrideCallback,
}) async {
final tempDir = await getTemporaryDirectory();
final tempFile = File(p.join(tempDir.path, 'restore_temp.weblibre'));
final outputDirectory = Directory(
p.join(filesystem.profilesDir.path, 'restore_temp'),
);
try {
await _safStream.copyToLocalFile(backupFileUri.toString(), tempFile.path);
final backup = SecureArchiveUnpack(
inputFile: tempFile,
outputDirectory: outputDirectory,
argon2Params: Argon2Params.memoryConstrained(),
);
await backup.unpack(password).then((_) async {
final existingProfile = await filesystem.readProfileMetadata(
outputDirectory,
);
if (existingProfile == null) {
throw Exception('Backup does not contain valid profile metadata');
}
if (existingProfile.uuidValue == filesystem.selectedProfile) {
throw Exception(
'Unable to override active User, please switch to another User and try again',
);
}
final profileDir = filesystem.getProfileDir(existingProfile.uuidValue);
if (await profileDir.exists()) {
final result = await confirmOverrideCallback();
if (result == true) {
await profileDir.delete(recursive: true);
await outputDirectory.rename(profileDir.path);
await filesystem.healProfile(profileDir);
}
} else {
// Profile doesn't exist yet, just move the restored data into place
await outputDirectory.rename(profileDir.path);
await filesystem.healProfile(profileDir);
}
});
ref.invalidate(profileRepositoryProvider);
return true;
} finally {
try {
if (await tempFile.exists()) {
await tempFile.delete();
}
} catch (e, s) {
logger.w(
'Failed to cleanup temporary restore file: ${tempFile.path}',
error: e,
stackTrace: s,
);
}
try {
if (await outputDirectory.exists()) {
await outputDirectory.delete(recursive: true);
}
} catch (e, s) {
logger.w(
'Failed to cleanup temporary backup directory: ${outputDirectory.path}',
error: e,
stackTrace: s,
);
}
}
}
Future<int> migrateOldBackups(Uri newDirUri) async {
try {
final oldDir = Directory(
p.join(
await getExternalStorageDirectory().then(
(dir) => Directory(
dir!.path.replaceFirst('/data/', '/media/'),
).parent.path,
),
'Backup',
),
);
if (!await oldDir.exists()) return 0;
var count = 0;
await for (final entity in oldDir.list()) {
if (entity is File && entity.path.endsWith('.weblibre')) {
try {
await _safStream.pasteLocalFile(
entity.path,
newDirUri.toString(),
p.basename(entity.path),
'application/octet-stream',
);
await entity.delete();
count++;
} catch (e, s) {
logger.w(
'Failed to migrate backup: ${entity.path}',
error: e,
stackTrace: s,
);
}
}
}
// Clean up old directory if empty
if (await oldDir.list().isEmpty) {
await oldDir.delete();
}
return count;
} catch (e, s) {
logger.w('Failed to migrate old backups', error: e, stackTrace: s);
return 0;
}
}
@override
void build() {}
}
@@ -0,0 +1,62 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_backup.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(UserBackupService)
final userBackupServiceProvider = UserBackupServiceProvider._();
final class UserBackupServiceProvider
extends $NotifierProvider<UserBackupService, void> {
UserBackupServiceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'userBackupServiceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$userBackupServiceHash();
@$internal
@override
UserBackupService create() => UserBackupService();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
}
String _$userBackupServiceHash() => r'3746ae59d490e25a44815c125f30cd0981de555d';
abstract class _$UserBackupService extends $Notifier<void> {
void build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}