prepare for multiple apps
This commit is contained in:
@@ -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', 'You’ll be logged out of most sites'),
|
||||
cache('Cached images and files', 'Frees up storage space'),
|
||||
permissions('Site permissions'),
|
||||
downloads('Downloads');
|
||||
|
||||
final String title;
|
||||
final String? description;
|
||||
|
||||
const DeleteBrowsingDataType(this.title, [this.description]);
|
||||
}
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class GeneralSettings with FastEquatable {
|
||||
final ThemeMode themeMode;
|
||||
final 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];
|
||||
}
|
||||
+52
@@ -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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
+52
@@ -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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
+62
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+55
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user