refactor persist bool provider

This commit is contained in:
Fabian Freund
2026-02-28 18:51:15 +01:00
parent 71974935de
commit 67c6222245
13 changed files with 149 additions and 280 deletions
@@ -24,23 +24,38 @@ import 'package:riverpod_annotation/experimental/persist.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/user/data/providers.dart';
part 'extensions_expanded.g.dart';
part 'persisted_bool.g.dart';
enum PersistedBoolKey {
extensionsExpanded(key: 'ExtensionsExpanded', defaultValue: false),
searchSuggestionsExpanded(
key: 'SearchSuggestionsExpanded',
defaultValue: true,
),
tabSuggestions(key: 'TabSuggestions', defaultValue: false);
const PersistedBoolKey({required this.key, required this.defaultValue});
final String key;
final bool defaultValue;
}
@Riverpod(keepAlive: true)
class ExtensionsExpanded extends _$ExtensionsExpanded {
void toggle() {
state = !state;
}
class PersistedBool extends _$PersistedBool {
void toggle() => state = !state;
// ignore: use_setters_to_change_properties
void set(bool value) => state = value;
@override
bool build() {
bool build(PersistedBoolKey key) {
persist(
ref.watch(riverpodDatabaseStorageProvider),
key: 'ExtensionsExpanded',
key: key.key,
encode: (state) => jsonEncode([state]),
decode: (encoded) => (jsonDecode(encoded) as List<dynamic>).first as bool,
);
return stateOrNull ?? false;
return stateOrNull ?? key.defaultValue;
}
}
@@ -0,0 +1,107 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'persisted_bool.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(PersistedBool)
final persistedBoolProvider = PersistedBoolFamily._();
final class PersistedBoolProvider
extends $NotifierProvider<PersistedBool, bool> {
PersistedBoolProvider._({
required PersistedBoolFamily super.from,
required PersistedBoolKey super.argument,
}) : super(
retry: null,
name: r'persistedBoolProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$persistedBoolHash();
@override
String toString() {
return r'persistedBoolProvider'
''
'($argument)';
}
@$internal
@override
PersistedBool create() => PersistedBool();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
@override
bool operator ==(Object other) {
return other is PersistedBoolProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$persistedBoolHash() => r'97fd7ed82dfde444fddf4475a3982be00f5f197a';
final class PersistedBoolFamily extends $Family
with
$ClassFamilyOverride<
PersistedBool,
bool,
bool,
bool,
PersistedBoolKey
> {
PersistedBoolFamily._()
: super(
retry: null,
name: r'persistedBoolProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: false,
);
PersistedBoolProvider call(PersistedBoolKey key) =>
PersistedBoolProvider._(argument: key, from: this);
@override
String toString() => r'persistedBoolProvider';
}
abstract class _$PersistedBool extends $Notifier<bool> {
late final _$args = ref.$arg as PersistedBoolKey;
PersistedBoolKey get key => _$args;
bool build(PersistedBoolKey key);
@$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(_$args));
}
}
@@ -28,35 +28,6 @@ import 'package:weblibre/features/user/data/providers.dart';
part 'tab_view_controllers.g.dart';
@Riverpod(keepAlive: true)
class TabSuggestionsController extends _$TabSuggestionsController {
void enable() {
state = true;
}
void disable() {
state = false;
}
void hide() {
if (state) {
state = false;
}
}
@override
bool build() {
persist(
ref.watch(riverpodDatabaseStorageProvider),
key: 'TabSuggestions',
encode: (state) => jsonEncode([state]),
decode: (encoded) => (jsonDecode(encoded) as List<dynamic>).first as bool,
);
return stateOrNull ?? false;
}
}
enum TabsViewMode {
list(MdiIcons.folderTable, 'List'),
grid(MdiIcons.table, 'Grid'),
@@ -9,59 +9,6 @@ part of 'tab_view_controllers.dart';
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(TabSuggestionsController)
final tabSuggestionsControllerProvider = TabSuggestionsControllerProvider._();
final class TabSuggestionsControllerProvider
extends $NotifierProvider<TabSuggestionsController, bool> {
TabSuggestionsControllerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'tabSuggestionsControllerProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabSuggestionsControllerHash();
@$internal
@override
TabSuggestionsController create() => TabSuggestionsController();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$tabSuggestionsControllerHash() =>
r'27a22674f9e4efb01e70120338202e95acef4791';
abstract class _$TabSuggestionsController 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(TabsViewModeController)
final tabsViewModeControllerProvider = TabsViewModeControllerProvider._();
@@ -1,63 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'extensions_expanded.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(ExtensionsExpanded)
final extensionsExpandedProvider = ExtensionsExpandedProvider._();
final class ExtensionsExpandedProvider
extends $NotifierProvider<ExtensionsExpanded, bool> {
ExtensionsExpandedProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'extensionsExpandedProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$extensionsExpandedHash();
@$internal
@override
ExtensionsExpanded create() => ExtensionsExpanded();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$extensionsExpandedHash() =>
r'c07d67f3d00a8c374b598664f7f54c1bcc6de5b7';
abstract class _$ExtensionsExpanded 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);
}
}
@@ -34,6 +34,7 @@ import 'package:nullability/nullability.dart';
import 'package:share_plus/share_plus.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/core/providers/persisted_bool.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
@@ -47,7 +48,6 @@ import 'package:weblibre/features/geckoview/domain/providers/web_extensions_stat
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/providers/extensions_expanded.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
import 'package:weblibre/features/geckoview/features/pwa/domain/providers.dart';
@@ -1368,7 +1368,7 @@ class _ExtensionsCard extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final addonService = ref.watch(addonServiceProvider);
final extensionsExpanded = ref.watch(extensionsExpandedProvider);
final extensionsExpanded = ref.watch(persistedBoolProvider(PersistedBoolKey.extensionsExpanded));
final pageExtensions = ref.watch(
webExtensionsStateProvider(
WebExtensionActionType.page,
@@ -1394,7 +1394,7 @@ class _ExtensionsCard extends HookConsumerWidget {
title: const Text('Extensions'),
initiallyExpanded: extensionsExpanded,
onExpansionChanged: (_) =>
ref.read(extensionsExpandedProvider.notifier).toggle(),
ref.read(persistedBoolProvider(PersistedBoolKey.extensionsExpanded).notifier).toggle(),
children: [
// Page extensions
if (pageExtensions.isNotEmpty) ...[
@@ -29,6 +29,7 @@ import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/core/providers/global_drop.dart';
import 'package:weblibre/core/providers/persisted_bool.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/data/models/drag_data.dart';
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
@@ -153,7 +154,7 @@ class _TabGridView extends HookConsumerWidget {
),
);
final tabSuggestionsEnabled = ref.watch(tabSuggestionsControllerProvider);
final tabSuggestionsEnabled = ref.watch(persistedBoolProvider(PersistedBoolKey.tabSuggestions));
final suggestedTabEntities = tabSuggestionsEnabled
? ref.watch(suggestedTabEntitiesProvider(containerId))
@@ -28,6 +28,7 @@ import 'package:flutter_reorderable_grid_view/widgets/custom_draggable.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/core/providers/global_drop.dart';
import 'package:weblibre/core/providers/persisted_bool.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/data/models/drag_data.dart';
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
@@ -214,7 +215,7 @@ class _TabListView extends HookConsumerWidget {
),
);
final tabSuggestionsEnabled = ref.watch(tabSuggestionsControllerProvider);
final tabSuggestionsEnabled = ref.watch(persistedBoolProvider(PersistedBoolKey.tabSuggestions));
final suggestedTabEntities = tabSuggestionsEnabled
? ref.watch(suggestedTabEntitiesProvider(containerId))
@@ -27,6 +27,7 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/providers/persisted_bool.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
@@ -345,7 +346,7 @@ class TabViewHeader extends HookConsumerWidget {
Consumer(
builder: (context, ref, child) {
final tabSuggestionsEnabled = ref.watch(
tabSuggestionsControllerProvider,
persistedBoolProvider(PersistedBoolKey.tabSuggestions),
);
final downloadProgress = ref.watch(
mlDownloadStateProvider,
@@ -380,18 +381,18 @@ class TabViewHeader extends HookConsumerWidget {
if (result == true) {
ref
.read(
tabSuggestionsControllerProvider
persistedBoolProvider(PersistedBoolKey.tabSuggestions)
.notifier,
)
.enable();
.set(true);
}
} else {
ref
.read(
tabSuggestionsControllerProvider
persistedBoolProvider(PersistedBoolKey.tabSuggestions)
.notifier,
)
.disable();
.set(false);
}
},
),
@@ -409,9 +410,7 @@ class TabViewHeader extends HookConsumerWidget {
);
return IconButton.filledTonal(
icon: const Icon(
MdiIcons.orderNumericAscending,
),
icon: const Icon(Icons.swap_vert),
isSelected: tabsReorderabe,
iconSize: 18,
padding: EdgeInsets.zero,
@@ -1,46 +0,0 @@
/*
* 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: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 'search_suggestions_view.g.dart';
@Riverpod(keepAlive: true)
class SearchSuggestionsExpanded extends _$SearchSuggestionsExpanded {
void toggle() {
state = !state;
}
@override
bool build() {
persist(
ref.watch(riverpodDatabaseStorageProvider),
key: 'SearchSuggestionsExpanded',
encode: (state) => jsonEncode([state]),
decode: (encoded) => (jsonDecode(encoded) as List<dynamic>).first as bool,
);
return stateOrNull ?? true;
}
}
@@ -1,63 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search_suggestions_view.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(SearchSuggestionsExpanded)
final searchSuggestionsExpandedProvider = SearchSuggestionsExpandedProvider._();
final class SearchSuggestionsExpandedProvider
extends $NotifierProvider<SearchSuggestionsExpanded, bool> {
SearchSuggestionsExpandedProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'searchSuggestionsExpandedProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$searchSuggestionsExpandedHash();
@$internal
@override
SearchSuggestionsExpanded create() => SearchSuggestionsExpanded();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$searchSuggestionsExpandedHash() =>
r'520bcdb9ef624c2ebe5a570d4dab034de5b4f475';
abstract class _$SearchSuggestionsExpanded 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);
}
}
@@ -24,11 +24,11 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:sliver_tools/sliver_tools.dart';
import 'package:weblibre/core/providers/persisted_bool.dart';
import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_suggestions.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_suggestions_view.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/smart_bang_selector.dart';
class FullSearchTermSuggestions extends HookConsumerWidget {
@@ -58,7 +58,7 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
final searchSuggestions = ref.watch(searchSuggestionsProvider());
final searchHistory = ref.watch(searchHistoryProvider);
final expanded = ref.watch(searchSuggestionsExpandedProvider);
final expanded = ref.watch(persistedBoolProvider(PersistedBoolKey.searchSuggestionsExpanded));
useOnListenableChange(searchTextController, () {
ref
@@ -111,7 +111,7 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 6.0),
child: IconButton(
onPressed: ref
.read(searchSuggestionsExpandedProvider.notifier)
.read(persistedBoolProvider(PersistedBoolKey.searchSuggestionsExpanded).notifier)
.toggle,
icon: Icon(expanded ? Icons.unfold_less : Icons.unfold_more),
),
@@ -25,8 +25,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/providers/persisted_bool.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
@@ -109,7 +109,7 @@ class _ContainerSuggestionsChip extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabSuggestionsEnabled = ref.watch(tabSuggestionsControllerProvider);
final tabSuggestionsEnabled = ref.watch(persistedBoolProvider(PersistedBoolKey.tabSuggestions));
final enableAiFeatures = ref.watch(
generalSettingsWithDefaultsProvider.select(
(settings) => settings.enableLocalAiFeatures,