diff --git a/analysis_options.yaml b/analysis_options.yaml
index 0f775d6f..2d3bf859 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -11,6 +11,7 @@ linter:
analyzer:
errors:
+ avoid_redundant_argument_values: ignore
experimental_member_use: ignore
exclude:
- "**.g.dart"
diff --git a/apps/weblibre/lib/domain/services/generic_website.dart b/apps/weblibre/lib/domain/services/generic_website.dart
index 571fece7..7d19a821 100644
--- a/apps/weblibre/lib/domain/services/generic_website.dart
+++ b/apps/weblibre/lib/domain/services/generic_website.dart
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+import 'dart:async';
import 'dart:io';
import 'dart:ui';
@@ -193,7 +194,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
inFlight = _fetchAndCacheDdgIcon(url, cacheMissing: cacheMissing)
.whenComplete(() {
if (identical(_inFlightIconFetches[origin], inFlight)) {
- _inFlightIconFetches.remove(origin);
+ _inFlightIconFetches.remove(origin)?.ignore();
}
});
diff --git a/apps/weblibre/lib/features/account/data/models/account_auth_state.dart b/apps/weblibre/lib/features/account/data/models/account_auth_state.dart
index 0767b422..9a3b5676 100644
--- a/apps/weblibre/lib/features/account/data/models/account_auth_state.dart
+++ b/apps/weblibre/lib/features/account/data/models/account_auth_state.dart
@@ -37,7 +37,6 @@ class AccountAuthState with FastEquatable {
// states for the same user but with distinct client instances are still
// semantically equal — including identityHashCode here would defeat
// Riverpod's caching by treating every reissued state as different.
- // ignore: missing_field_in_equatable_props
final SupabaseClient? client;
AccountAuthState({
@@ -64,5 +63,6 @@ class AccountAuthState with FastEquatable {
userId,
lastError,
syncKey,
+ client,
];
}
diff --git a/apps/weblibre/lib/features/account/domain/repositories/account_auth.dart b/apps/weblibre/lib/features/account/domain/repositories/account_auth.dart
index e8417671..bc2fb2ae 100644
--- a/apps/weblibre/lib/features/account/domain/repositories/account_auth.dart
+++ b/apps/weblibre/lib/features/account/domain/repositories/account_auth.dart
@@ -271,7 +271,6 @@ class AccountAuthRepository extends _$AccountAuthRepository {
// Clear the pending code verifier so a late browser callback is rejected.
final data = await _store.read();
- // ignore: avoid_redundant_argument_values
await _store.write(data.copyWith(pendingCodeVerifier: null));
state = AsyncData(AccountAuthState());
@@ -327,7 +326,6 @@ class AccountAuthRepository extends _$AccountAuthRepository {
displayName:
(user?['user_metadata'] as Map?)?['display_name']
as String?,
- // ignore: avoid_redundant_argument_values
pendingCodeVerifier: null,
),
);
@@ -372,9 +370,7 @@ class AccountAuthRepository extends _$AccountAuthRepository {
Future clearSyncKey() async {
final data = await _store.read();
- // ignore: avoid_redundant_argument_values
await _store.write(data.copyWith(syncKey: null));
- // ignore: avoid_redundant_argument_values
state = AsyncData(_currentOrEmpty.copyWith(syncKey: null));
}
diff --git a/apps/weblibre/lib/features/bangs/data/models/web_search_bang.dart b/apps/weblibre/lib/features/bangs/data/models/web_search_bang.dart
index 5bc87ac3..d8d71aea 100644
--- a/apps/weblibre/lib/features/bangs/data/models/web_search_bang.dart
+++ b/apps/weblibre/lib/features/bangs/data/models/web_search_bang.dart
@@ -15,7 +15,7 @@ final webSearchBang = BangData(
searxngApi: false,
);
-final webSearchBangKey = BangKey(
+const webSearchBangKey = BangKey(
group: BangGroup.weblibre,
trigger: webSearchBangTrigger,
);
diff --git a/apps/weblibre/lib/features/bangs/presentation/screens/search.dart b/apps/weblibre/lib/features/bangs/presentation/screens/search.dart
index 435ac272..aafc9026 100644
--- a/apps/weblibre/lib/features/bangs/presentation/screens/search.dart
+++ b/apps/weblibre/lib/features/bangs/presentation/screens/search.dart
@@ -17,14 +17,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import 'dart:async';
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:weblibre/features/bangs/data/models/bang_data.dart';
import 'package:weblibre/features/bangs/domain/providers/search.dart';
import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart';
import 'package:weblibre/features/user/domain/providers.dart';
diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart
index c0da2bae..bc69c27a 100644
--- a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart
+++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart
@@ -1033,7 +1033,7 @@ EquatableValue> groupedTabListItems(
final unpinned = children
.where((c) => !pinnedTabIds.contains(c.row.id))
.toList();
- final cmp = (_GroupedRow a, _GroupedRow b) =>
+ int cmp(_GroupedRow a, _GroupedRow b) =>
a.row.orderKey.compareTo(b.row.orderKey);
final directionCmp = tabListDirection == TabDirection.newestFirst
? (_GroupedRow a, _GroupedRow b) => -cmp(a, b)
diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart
index dd1032b9..f3cc281b 100644
--- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart
+++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart
@@ -85,12 +85,10 @@ class TabViewFilterController extends _$TabViewFilterController {
}
void setDateRange(DateTimeRange? range) {
- // ignore: avoid_redundant_argument_values
state = state.copyWith(dateRange: range, quickInterval: null);
}
void setQuickInterval(TabQuickInterval? interval) {
- // ignore: avoid_redundant_argument_values
state = state.copyWith(quickInterval: interval, dateRange: null);
}
diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart
index 8f3a5f06..9c7d6af1 100644
--- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart
+++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart
@@ -1194,13 +1194,11 @@ class _Browser extends HookConsumerWidget {
switch (promptOnBackBehavior) {
case BackgroundAppTabBackPromptBehavior():
await moveToBackground();
- break;
case ReturnToSearchTabBackPromptBehavior(:final tabType):
ref
.read(searchAutofocusSuppressionProvider.notifier)
.suppressNext();
await SearchRoute(tabType: tabType).push(context);
- break;
}
return true;
diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart
index 0e5cd3ac..eb0ef24b 100644
--- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart
+++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart
@@ -219,7 +219,7 @@ List _orderedIdsForStorageAnchors(
required String movingPartitionRootId,
required bool sortPinnedFirst,
}) {
- var storageOrderedIds = tabListDirection == TabDirection.newestFirst
+ final storageOrderedIds = tabListDirection == TabDirection.newestFirst
// Rendering flips root group order for newest-first; convert the
// display order back to storage order before choosing anchors.
? orderedTabIds.reversed.toList()
diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart
index 80edf329..56797bbd 100644
--- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart
+++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart
@@ -580,11 +580,13 @@ class QuickTabSwitcher extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) {
final context = activeItemKey.value.currentContext;
if (context != null) {
- Scrollable.ensureVisible(
- context,
- alignment: 0.5,
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeInOut,
+ unawaited(
+ Scrollable.ensureVisible(
+ context,
+ alignment: 0.5,
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeInOut,
+ ),
);
} else if (chipScrollController.hasClients) {
final activeIndex = availableItems.indexWhere(
@@ -602,11 +604,13 @@ class QuickTabSwitcher extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) {
final retryContext = activeItemKey.value.currentContext;
if (retryContext != null) {
- Scrollable.ensureVisible(
- retryContext,
- alignment: 0.5,
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeInOut,
+ unawaited(
+ Scrollable.ensureVisible(
+ retryContext,
+ alignment: 0.5,
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeInOut,
+ ),
);
}
});
@@ -717,8 +721,6 @@ class QuickTabSwitcherView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appColors = AppColors.of(context);
- final colorScheme = Theme.of(context).colorScheme;
-
if (availableItems.isEmpty) {
return const SizedBox.shrink();
}
diff --git a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart
index 32bd8cae..a25b63b7 100644
--- a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart
+++ b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart
@@ -27,8 +27,8 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:weblibre/core/logger.dart';
-import 'package:weblibre/features/geckoview/features/preferences/data/repositories/preference_migrations.dart';
import 'package:weblibre/features/geckoview/features/preferences/data/models/preference_setting.dart';
+import 'package:weblibre/features/geckoview/features/preferences/data/repositories/preference_migrations.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/setting_groups_serializer.dart';
import 'package:weblibre/features/user/data/providers.dart';
diff --git a/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.dart b/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.dart
index 441e4986..322547f9 100644
--- a/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.dart
+++ b/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.dart
@@ -107,7 +107,7 @@ Future installCurrentWebApp(
Ref ref, {
String? overrideName,
String? contextId,
-}) async {
+}) {
final selectedTabId = ref.read(selectedTabProvider);
if (selectedTabId == null) {
@@ -156,7 +156,7 @@ Future installBasicShortcut(
Ref ref, {
String? overrideName,
String? contextId,
-}) async {
+}) {
final selectedTabId = ref.read(selectedTabProvider);
if (selectedTabId == null) {
diff --git a/apps/weblibre/lib/features/geckoview/features/search/presentation/screens/search.dart b/apps/weblibre/lib/features/geckoview/features/search/presentation/screens/search.dart
index d17243cc..cd14f126 100644
--- a/apps/weblibre/lib/features/geckoview/features/search/presentation/screens/search.dart
+++ b/apps/weblibre/lib/features/geckoview/features/search/presentation/screens/search.dart
@@ -801,8 +801,8 @@ class _WebSearchOptionsRow extends StatelessWidget {
controller: controller,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
- child: Row(
- children: const [
+ child: const Row(
+ children: [
WebSearchStatusChip(),
RouteThroughTorToggle(),
SizedBox(width: 8),
diff --git a/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart b/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart
index 1958ff37..fa118cdf 100644
--- a/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart
+++ b/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart
@@ -60,9 +60,7 @@ class FeedSearch extends HookConsumerWidget {
.read(articleSearchProvider(null).notifier)
.search(
searchTextNotifier.value.text,
- // ignore: avoid_redundant_argument_values dont break things
matchPrefix: _matchPrefix,
- // ignore: avoid_redundant_argument_values dont break things
matchSuffix: _matchSuffix,
);
},
diff --git a/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart b/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart
index bd721833..f7919985 100644
--- a/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart
+++ b/apps/weblibre/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart
@@ -123,9 +123,7 @@ class TabSearch extends HookConsumerWidget {
)
.addQuery(
searchTextListenable.value.text,
- // ignore: avoid_redundant_argument_values dont break things
matchPrefix: _matchPrefix,
- // ignore: avoid_redundant_argument_values dont break things
matchSuffix: _matchSuffix,
);
}
diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart
index 4bf16c31..62e156c1 100644
--- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart
+++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart
@@ -18,6 +18,8 @@
* along with this program. If not, see .
*/
+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';
@@ -181,7 +183,7 @@ class ContainerEditScreen extends HookConsumerWidget {
title: const Text('Change Color'),
onTap: () {
Navigator.of(context).pop();
- openColorPicker();
+ unawaited(openColorPicker());
},
),
ListTile(
@@ -189,7 +191,7 @@ class ContainerEditScreen extends HookConsumerWidget {
title: const Text('Change Icon'),
onTap: () {
Navigator.of(context).pop();
- openIconPicker();
+ unawaited(openIconPicker());
},
),
const SizedBox(height: 8),
diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart
index 57769e3b..877b2b6c 100644
--- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart
+++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart
@@ -105,7 +105,6 @@ class ContainerListScreen extends HookConsumerWidget {
.read(selectedContainerProvider.notifier)
.clearContainer()
: () => setSelectedContainer(container),
- // onDelete: () => repository.deleteContainer(container.id),
),
);
},
@@ -168,7 +167,6 @@ class _ContainerCard extends HookConsumerWidget {
required this.isSelected,
required this.onTap,
required this.onSelect,
- this.onDelete,
});
final ContainerDataWithCount container;
@@ -176,7 +174,6 @@ class _ContainerCard extends HookConsumerWidget {
final bool isSelected;
final VoidCallback onTap;
final VoidCallback onSelect;
- final VoidCallback? onDelete;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -299,15 +296,6 @@ class _ContainerCard extends HookConsumerWidget {
else
const SizedBox.shrink(),
const Spacer(),
- if (onDelete != null) ...[
- IconButton(
- tooltip: 'Delete',
- color: colorScheme.error,
- onPressed: onDelete,
- icon: const Icon(Icons.delete_outline),
- ),
- const SizedBox(width: 4),
- ],
FilledButton.tonalIcon(
onPressed: onSelect,
icon: Icon(isSelected ? Icons.close : Icons.check),
diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart
index cccdd1d7..b5388f96 100644
--- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart
+++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart
@@ -20,9 +20,7 @@
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:nullability/nullability.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/entities/container_selection_result.dart';
diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart
index 1592ffe3..f3e481ef 100644
--- a/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart
+++ b/apps/weblibre/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart
@@ -35,7 +35,6 @@ import 'package:weblibre/features/geckoview/features/tabs/data/models/container_
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chip_content.dart';
-import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
@@ -303,11 +302,13 @@ class ContainerChips extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) {
final activeContext = activeItemKey.value.currentContext;
if (activeContext != null) {
- Scrollable.ensureVisible(
- activeContext,
- alignment: 0.5,
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeInOut,
+ unawaited(
+ Scrollable.ensureVisible(
+ activeContext,
+ alignment: 0.5,
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeInOut,
+ ),
);
return;
}
@@ -336,11 +337,13 @@ class ContainerChips extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) {
final retryContext = activeItemKey.value.currentContext;
if (retryContext != null) {
- Scrollable.ensureVisible(
- retryContext,
- alignment: 0.5,
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeInOut,
+ unawaited(
+ Scrollable.ensureVisible(
+ retryContext,
+ alignment: 0.5,
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeInOut,
+ ),
);
}
});
diff --git a/apps/weblibre/lib/features/onboarding/presentation/pages/welcome.dart b/apps/weblibre/lib/features/onboarding/presentation/pages/welcome.dart
index 13fa9c61..41f85dc4 100644
--- a/apps/weblibre/lib/features/onboarding/presentation/pages/welcome.dart
+++ b/apps/weblibre/lib/features/onboarding/presentation/pages/welcome.dart
@@ -17,9 +17,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart';
diff --git a/apps/weblibre/lib/features/proxy/domain/providers/proxy_connection_options.dart b/apps/weblibre/lib/features/proxy/domain/providers/proxy_connection_options.dart
index 529979cc..7c9471fe 100644
--- a/apps/weblibre/lib/features/proxy/domain/providers/proxy_connection_options.dart
+++ b/apps/weblibre/lib/features/proxy/domain/providers/proxy_connection_options.dart
@@ -18,7 +18,6 @@
* along with this program. If not, see .
*/
import 'package:fast_equatable/fast_equatable.dart';
-import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/proxy/data/models/singbox_proxy_profile.dart';
diff --git a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_profiles.dart b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_profiles.dart
index 44fdb25c..9add8375 100644
--- a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_profiles.dart
+++ b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_profiles.dart
@@ -18,7 +18,6 @@
* along with this program. If not, see .
*/
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
-import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/uuid.dart';
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_credentials.dart';
diff --git a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart
index 618fa02f..7095c027 100644
--- a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart
+++ b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart
@@ -127,7 +127,7 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
Future startProfile(
String profileId, {
SingboxProxyRuntimeOptions? options,
- }) async {
+ }) {
return _lock.synchronized(() async {
final currentState = await _stateSnapshotUnlocked();
final activeProfileIds = _activeProfileIds(currentState);
@@ -320,7 +320,7 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
final profileMap = {for (final profile in profiles) profile.id: profile};
return Future.wait(
- profileIds.map((profileId) async {
+ profileIds.map((profileId) {
final profile = profileMap[profileId];
if (profile == null) {
throw StateError('Unknown sing-box proxy profile: $profileId');
@@ -340,15 +340,15 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
}
@override
- Future build() async {
+ Future build() {
final plugin = ref.watch(singboxProxyClientProvider);
final stateSubscription = plugin.stateStream.listen((nextState) {
state = AsyncData(nextState);
});
- ref.onDispose(() async {
- await stateSubscription.cancel();
- await plugin.dispose();
+ ref.onDispose(() {
+ unawaited(stateSubscription.cancel());
+ unawaited(plugin.dispose());
});
return plugin.getState();
diff --git a/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.dart b/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.dart
index da0ab24e..fa380bff 100644
--- a/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.dart
+++ b/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.dart
@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+import 'dart:async';
+
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -104,13 +106,15 @@ class SingboxProxyEndpointSync extends _$SingboxProxyEndpointSync {
(previous, next) {
final runtimeState = next.value;
if (runtimeState == null) return;
- _sync(runtimeState).catchError((Object error, StackTrace stackTrace) {
- logger.e(
- 'Failed to sync sing-box proxy endpoints to Gecko',
- error: error,
- stackTrace: stackTrace,
- );
- });
+ unawaited(
+ _sync(runtimeState).catchError((Object error, StackTrace stackTrace) {
+ logger.e(
+ 'Failed to sync sing-box proxy endpoints to Gecko',
+ error: error,
+ stackTrace: stackTrace,
+ );
+ }),
+ );
},
);
}
diff --git a/apps/weblibre/lib/features/search_credits/data/search_backend_config.dart b/apps/weblibre/lib/features/search_credits/data/search_backend_config.dart
index 65893bfe..2553fa80 100644
--- a/apps/weblibre/lib/features/search_credits/data/search_backend_config.dart
+++ b/apps/weblibre/lib/features/search_credits/data/search_backend_config.dart
@@ -17,25 +17,23 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-abstract final class SearchBackendConfig {
- /// Clearnet origin used when the user has not opted to route search through
- /// Tor. Must be a normal HTTPS URL (or HTTP for dev).
- static const searchBackendOrigin = String.fromEnvironment(
- 'SEARCH_BACKEND_ORIGIN',
- defaultValue: 'https://search.weblibre.eu',
- );
+/// Clearnet origin used when the user has not opted to route search through
+/// Tor. Must be a normal HTTPS URL (or HTTP for dev).
+const searchBackendOrigin = String.fromEnvironment(
+ 'SEARCH_BACKEND_ORIGIN',
+ defaultValue: 'https://search.weblibre.eu',
+);
- /// Origin used when the user opts to route search through Tor. Should be
- /// the WebLibre search service's onion address so the Tor circuit
- /// terminates inside the Tor network instead of exiting back to the
- /// clearnet. Falls back to the clearnet origin when no onion address is
- /// configured at build time.
- static const searchBackendOriginTor = String.fromEnvironment(
- 'SEARCH_BACKEND_ORIGIN_TOR',
- defaultValue:
- 'http://eyipgwt32zaejr2xwblaswp2ur4qikapofunbqus5dklvf7jxkncirad.onion',
- );
+/// Origin used when the user opts to route search through Tor. Should be
+/// the WebLibre search service's onion address so the Tor circuit
+/// terminates inside the Tor network instead of exiting back to the
+/// clearnet. Falls back to the clearnet origin when no onion address is
+/// configured at build time.
+const searchBackendOriginTor = String.fromEnvironment(
+ 'SEARCH_BACKEND_ORIGIN_TOR',
+ defaultValue:
+ 'http://eyipgwt32zaejr2xwblaswp2ur4qikapofunbqus5dklvf7jxkncirad.onion',
+);
- static Uri get originUri => Uri.parse(searchBackendOrigin);
- static Uri get torOriginUri => Uri.parse(searchBackendOriginTor);
-}
+Uri get searchBackendOriginUri => Uri.parse(searchBackendOrigin);
+Uri get searchBackendTorOriginUri => Uri.parse(searchBackendOriginTor);
diff --git a/apps/weblibre/lib/features/search_credits/domain/providers.dart b/apps/weblibre/lib/features/search_credits/domain/providers.dart
index ddd41152..65e4912d 100644
--- a/apps/weblibre/lib/features/search_credits/domain/providers.dart
+++ b/apps/weblibre/lib/features/search_credits/domain/providers.dart
@@ -33,9 +33,7 @@ BackendEndpoints searchBackendEndpoints(Ref ref) {
webSearchSettingsControllerProvider.select((s) => s.routeThroughTor),
);
return BackendEndpoints.fromOrigin(
- routeThroughTor
- ? SearchBackendConfig.torOriginUri
- : SearchBackendConfig.originUri,
+ routeThroughTor ? searchBackendTorOriginUri : searchBackendOriginUri,
);
}
diff --git a/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart b/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart
index c38996c0..ab55405c 100644
--- a/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart
+++ b/apps/weblibre/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart
@@ -99,8 +99,8 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
pinned: true,
delegate: _ToolbarPreviewDelegate(configs: configs.value),
),
- SliverPadding(
- padding: const EdgeInsets.fromLTRB(16, 20, 16, 0),
+ const SliverPadding(
+ padding: EdgeInsets.fromLTRB(16, 20, 16, 0),
sliver: SliverToBoxAdapter(child: _SectionLabel(label: 'Enabled')),
),
if (visibleConfigs.isEmpty)
@@ -137,8 +137,8 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
);
},
),
- SliverPadding(
- padding: const EdgeInsets.fromLTRB(16, 20, 16, 0),
+ const SliverPadding(
+ padding: EdgeInsets.fromLTRB(16, 20, 16, 0),
sliver: SliverToBoxAdapter(child: _SectionLabel(label: 'Disabled')),
),
if (hiddenConfigs.isEmpty)
diff --git a/apps/weblibre/lib/features/settings/presentation/screens/extensions_settings.dart b/apps/weblibre/lib/features/settings/presentation/screens/extensions_settings.dart
index 04a9148c..ac009b67 100644
--- a/apps/weblibre/lib/features/settings/presentation/screens/extensions_settings.dart
+++ b/apps/weblibre/lib/features/settings/presentation/screens/extensions_settings.dart
@@ -25,7 +25,6 @@ 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/geckoview/features/browser/domain/services/browser_addon.dart';
-import 'package:weblibre/features/settings/presentation/widgets/custom_list_tile.dart';
import 'package:weblibre/features/settings/presentation/widgets/settings_detail.dart';
const List extensionsSettingsSections = [
diff --git a/apps/weblibre/lib/features/settings/presentation/screens/general_settings.dart b/apps/weblibre/lib/features/settings/presentation/screens/general_settings.dart
index f2320c40..d3a1156f 100644
--- a/apps/weblibre/lib/features/settings/presentation/screens/general_settings.dart
+++ b/apps/weblibre/lib/features/settings/presentation/screens/general_settings.dart
@@ -17,7 +17,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'
diff --git a/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.dart b/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.dart
index e641e5f0..9d5ca6c5 100644
--- a/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.dart
+++ b/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.dart
@@ -41,6 +41,7 @@ const _alwaysAllowPackageExtra = 'eu.weblibre.gatekeeper.always_allow_package';
StreamTransformer
_buildSharingIntentTransformer(
IntentGatekeeper gatekeeper,
+ GeneralSettingsRepository settingsRepository,
) => StreamTransformer.fromHandlers(
handleData: (intent, sink) async {
if (_extractAccountCallback(intent) != null) {
@@ -53,14 +54,12 @@ _buildSharingIntentTransformer(
final alwaysAllowPackage =
intent.extra[_alwaysAllowPackageExtra] as String?;
if (alwaysAllowPackage != null) {
- await gatekeeper.ref
- .read(generalSettingsRepositoryProvider.notifier)
- .updateSettings(
- (current) => current.copyWith.externalAppIntentPolicies({
- ...current.externalAppIntentPolicies,
- alwaysAllowPackage: IntentSourcePolicy.allow,
- }),
- );
+ await settingsRepository.updateSettings(
+ (current) => current.copyWith.externalAppIntentPolicies({
+ ...current.externalAppIntentPolicies,
+ alwaysAllowPackage: IntentSourcePolicy.allow,
+ }),
+ );
}
final shortcutContextId = intent.action == 'android.intent.action.VIEW'
@@ -197,10 +196,13 @@ Raw> _consumeIntents(
Raw> sharingIntentStream(Ref ref) {
final receiver = ref.watch(intentReceiverProvider);
final gatekeeper = ref.watch(intentGatekeeperProvider.notifier);
+ final settingsRepository = ref.watch(
+ generalSettingsRepositoryProvider.notifier,
+ );
return _consumeIntents(
ref,
receiver,
- _buildSharingIntentTransformer(gatekeeper),
+ _buildSharingIntentTransformer(gatekeeper, settingsRepository),
);
}
diff --git a/apps/weblibre/lib/features/small_web/presentation/controllers/small_web_session_controller.dart b/apps/weblibre/lib/features/small_web/presentation/controllers/small_web_session_controller.dart
index 2e7cb25d..48748be7 100644
--- a/apps/weblibre/lib/features/small_web/presentation/controllers/small_web_session_controller.dart
+++ b/apps/weblibre/lib/features/small_web/presentation/controllers/small_web_session_controller.dart
@@ -1,5 +1,3 @@
-// ignore_for_file: avoid_redundant_argument_values
-
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
diff --git a/apps/weblibre/lib/features/sync/domain/repositories/sync.dart b/apps/weblibre/lib/features/sync/domain/repositories/sync.dart
index 7d8b3197..c1d8c4ff 100644
--- a/apps/weblibre/lib/features/sync/domain/repositories/sync.dart
+++ b/apps/weblibre/lib/features/sync/domain/repositories/sync.dart
@@ -315,21 +315,15 @@ class SyncRepository extends _$SyncRepository {
_syncStartedSub = syncStateService.syncStartedEvents.listen((_) {
_update(
- (s) => s.copyWith(
- lastSyncEvent: SyncEvent.started,
- // ignore: avoid_redundant_argument_values
- lastSyncError: null,
- ),
+ (s) =>
+ s.copyWith(lastSyncEvent: SyncEvent.started, lastSyncError: null),
);
});
_syncCompletedSub = syncStateService.syncCompletedEvents.listen((_) {
_update(
- (s) => s.copyWith(
- lastSyncEvent: SyncEvent.completed,
- // ignore: avoid_redundant_argument_values
- lastSyncError: null,
- ),
+ (s) =>
+ s.copyWith(lastSyncEvent: SyncEvent.completed, lastSyncError: null),
);
unawaited(Future.wait([_refreshTabs(), _refreshDevices()]));
});
diff --git a/apps/weblibre/lib/features/user/data/database/daos/cache.dart b/apps/weblibre/lib/features/user/data/database/daos/cache.dart
index 278032e1..e40e25e2 100644
--- a/apps/weblibre/lib/features/user/data/database/daos/cache.dart
+++ b/apps/weblibre/lib/features/user/data/database/daos/cache.dart
@@ -19,10 +19,10 @@
*/
import 'package:drift/drift.dart';
import 'package:weblibre/data/database/extensions/database_table_size.dart';
-import 'package:weblibre/features/user/data/icon_cache_marker.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';
+import 'package:weblibre/features/user/data/icon_cache_marker.dart';
@DriftAccessor()
class CacheDao extends DatabaseAccessor with $CacheDaoMixin {
diff --git a/apps/weblibre/lib/features/user/data/database/daos/search_tokens.dart b/apps/weblibre/lib/features/user/data/database/daos/search_tokens.dart
index 25674115..7bcc83d3 100644
--- a/apps/weblibre/lib/features/user/data/database/daos/search_tokens.dart
+++ b/apps/weblibre/lib/features/user/data/database/daos/search_tokens.dart
@@ -67,7 +67,7 @@ class SearchTokensDao extends DatabaseAccessor {
/// Release any reservation older than [maxAge] — covers crashes mid-flight.
/// Returns the number of rows released.
- Future releaseStaleReservations(Duration maxAge) async {
+ Future releaseStaleReservations(Duration maxAge) {
final cutoff = DateTime.now().subtract(maxAge);
return (update(db.searchTokens)..where(
(t) =>
diff --git a/apps/weblibre/lib/features/user/data/models/ublock_asset.dart b/apps/weblibre/lib/features/user/data/models/ublock_asset.dart
index 5552e101..5a7be25b 100644
--- a/apps/weblibre/lib/features/user/data/models/ublock_asset.dart
+++ b/apps/weblibre/lib/features/user/data/models/ublock_asset.dart
@@ -261,7 +261,7 @@ class UBlockAssetsRegistry {
return count;
}
- static UBlockAssetsRegistry fromJson(Map json) {
+ factory UBlockAssetsRegistry.fromJson(Map json) {
final entries = {};
for (final entry in json.entries) {
if (entry.value is Map) {
diff --git a/apps/weblibre/lib/features/web_search/data/locale_options.dart b/apps/weblibre/lib/features/web_search/data/locale_options.dart
index 782418ef..e39f95e7 100644
--- a/apps/weblibre/lib/features/web_search/data/locale_options.dart
+++ b/apps/weblibre/lib/features/web_search/data/locale_options.dart
@@ -17,16 +17,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-library;
-/// Curated picker options for the web-search language and country selectors.
-///
-/// Codes intersect what Brave (`search_lang`/`country`) and Mojeek
-/// (`lb`/`rb`) accept; English display names so the menu doesn't need a
-/// per-locale translation pass. The protocol carries just the primary
-/// ISO 639-1 / ISO 3166-1 alpha-2 codes — region qualifiers (`en-gb`,
-/// `pt-br`, etc.) are reconstructed server-side from the language+country
-/// pair.
+// Curated picker options for the web-search language and country selectors.
+//
+// Codes intersect what Brave (`search_lang`/`country`) and Mojeek
+// (`lb`/`rb`) accept; English display names so the menu doesn't need a
+// per-locale translation pass. The protocol carries just the primary
+// ISO 639-1 / ISO 3166-1 alpha-2 codes — region qualifiers (`en-gb`,
+// `pt-br`, etc.) are reconstructed server-side from the language+country
+// pair.
class LanguageOption {
final String code; // ISO 639-1
diff --git a/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.dart b/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.dart
index 9fc23607..15d4e982 100644
--- a/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.dart
+++ b/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.dart
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'
as fmc;
import 'package:riverpod_annotation/riverpod_annotation.dart';
-import 'package:search_protocol/search_protocol.dart';
import 'package:search_client/search_client.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
@@ -18,9 +17,9 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart';
import 'package:weblibre/features/search_credits/domain/providers.dart';
+import 'package:weblibre/features/search_credits/domain/providers/proxy_client.dart';
import 'package:weblibre/features/search_credits/domain/repositories/search_token_stash_repository.dart';
import 'package:weblibre/features/user/domain/repositories/cache.dart';
-import 'package:weblibre/features/search_credits/domain/providers/proxy_client.dart';
import 'package:weblibre/features/web_search/domain/services/capture_artifact_downloader.dart';
import 'package:weblibre/features/web_search/domain/services/capture_server.dart';
import 'package:weblibre/features/web_search/domain/services/sandbox_capture_store.dart';
@@ -114,7 +113,7 @@ SandboxCaptureStore sandboxCaptureStore(Ref ref) => SandboxCaptureStore();
@Riverpod(keepAlive: true)
Stream sandboxCaptureErrors(Ref ref) {
final ctrl = ref.watch(sandboxCaptureControllerProvider.notifier);
- return ctrl.errors;
+ return ctrl.errorStream();
}
/// Orchestrates sandbox capture browsing:
@@ -148,10 +147,7 @@ class SandboxCaptureController extends _$SandboxCaptureController {
static const _defaultMethod = 'singlefile';
static const _defaultVariant = 'balanced';
- StreamSubscription>? _captureTabSub;
- StreamSubscription? _retrySub;
-
- Stream get errors => _errors.stream;
+ Stream errorStream() => _errors.stream;
@override
void build() {
@@ -159,9 +155,9 @@ class SandboxCaptureController extends _$SandboxCaptureController {
fmc.SandboxCaptureHostEvents.setUp(_hostEvents);
final dao = ref.read(tabDatabaseProvider).captureTabDao;
- _captureTabSub = dao.watchAll().listen(_onCaptureTabChange);
+ final captureTabSub = dao.watchAll().listen(_onCaptureTabChange);
- _retrySub = ref
+ final retrySub = ref
.read(captureServerProvider)
.retryRequests
.listen(_onRetryRequest);
@@ -173,10 +169,8 @@ class SandboxCaptureController extends _$SandboxCaptureController {
// stream — otherwise an in-flight Pigeon event or DAO emit could
// race with handler cleanup, or _markFailed could try to add to a
// closed _errors sink.
- final captureTabSub = _captureTabSub;
- final retrySub = _retrySub;
- if (captureTabSub != null) unawaited(captureTabSub.cancel());
- if (retrySub != null) unawaited(retrySub.cancel());
+ unawaited(captureTabSub.cancel());
+ unawaited(retrySub.cancel());
fmc.SandboxCaptureHostEvents.setUp(null);
_hostEvents.controller = null;
unawaited(_errors.close());
diff --git a/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.dart b/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.dart
index 657bea3f..b97c065c 100644
--- a/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.dart
+++ b/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.dart
@@ -4,7 +4,6 @@ import 'dart:typed_data';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
-import 'package:search_protocol/search_protocol.dart';
import 'package:search_client/search_client.dart';
import 'package:weblibre/domain/services/generic_website.dart';
import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart';
@@ -1226,7 +1225,10 @@ class WebSearchScrollOffset extends _$WebSearchScrollOffset {
@override
double build() => 0;
- void update(double offset) => state = offset;
+ void update(double offset) {
+ if (state == offset) return;
+ state = offset;
+ }
void reset() => state = 0;
}
diff --git a/apps/weblibre/lib/features/web_search/domain/services/capture_artifact_downloader.dart b/apps/weblibre/lib/features/web_search/domain/services/capture_artifact_downloader.dart
index 8571801d..b4a27bd1 100644
--- a/apps/weblibre/lib/features/web_search/domain/services/capture_artifact_downloader.dart
+++ b/apps/weblibre/lib/features/web_search/domain/services/capture_artifact_downloader.dart
@@ -5,7 +5,6 @@ import 'dart:ui';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'package:riverpod_annotation/riverpod_annotation.dart';
-import 'package:search_protocol/search_protocol.dart';
import 'package:search_client/search_client.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/features/search_credits/domain/providers.dart';
diff --git a/apps/weblibre/lib/features/web_search/domain/services/capture_server.dart b/apps/weblibre/lib/features/web_search/domain/services/capture_server.dart
index a4033840..f83e4f7f 100644
--- a/apps/weblibre/lib/features/web_search/domain/services/capture_server.dart
+++ b/apps/weblibre/lib/features/web_search/domain/services/capture_server.dart
@@ -361,14 +361,14 @@ class CaptureServer {
final tabIdJson = jsonEncode(tabId);
final captureIdJson = jsonEncode(captureId);
final html =
- ''
- ''
- 'Capturing…'
- '$body'
- ''
- ''
- '';
+ '''
+
+
+Capturing…
+$body
+
+
+''';
request.response.write(html);
await request.response.close();
}
@@ -510,35 +510,24 @@ class CaptureServer {
"base-uri 'none'; "
"form-action 'self'";
- static const _loaderStyles =
- 'html,body{height:100%}'
- 'body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,'
- 'sans-serif;display:flex;align-items:center;justify-content:center;'
- 'min-height:100vh;margin:0;background:#0b0d10;color:#e6e8eb}'
- 'main{text-align:center;max-width:480px;padding:32px;opacity:0;'
- 'animation:fade .35s ease-out forwards}'
- '@keyframes fade{to{opacity:1}}'
- '.spinner{width:56px;height:56px;margin:0 auto 20px;border-radius:50%;'
- 'border:3px solid rgba(255,255,255,.08);border-top-color:#7aa2f7;'
- 'animation:spin 1s linear infinite}'
- '@keyframes spin{to{transform:rotate(360deg)}}'
- 'h1{font-size:18px;font-weight:600;margin:0 0 8px;letter-spacing:.2px}'
- 'p{margin:0;color:#9aa1a8;font-size:14px;line-height:1.5}'
- '.dots::after{display:inline-block;width:1.2em;text-align:left;'
- 'animation:dots 1.4s steps(4,end) infinite;content:""}'
- '@keyframes dots{0%{content:""}25%{content:"."}50%{content:".."}'
- '75%{content:"..."}100%{content:""}}'
- '.error h1{color:#f7768e}'
- '.actions{margin-top:20px;display:flex;gap:8px;justify-content:center}'
- '.btn{padding:10px 16px;border:0;border-radius:10px;cursor:pointer;'
- 'font:inherit;font-weight:600;background:#7aa2f7;color:#0b0d10}'
- '.btn.secondary{background:transparent;color:#9aa1a8;'
- 'border:1px solid rgba(255,255,255,.12)}'
- '.hidden{display:none}'
- '@media(prefers-color-scheme:light){body{background:#f5f6f8;'
- 'color:#1a1d22}.spinner{border-color:rgba(0,0,0,.08);'
- 'border-top-color:#3b82f6}.btn{background:#3b82f6;color:white}'
- 'p{color:#52606b}}';
+ static const _loaderStyles = '''
+html,body{height:100%}
+body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#0b0d10;color:#e6e8eb}
+main{text-align:center;max-width:480px;padding:32px;opacity:0;animation:fade .35s ease-out forwards}
+@keyframes fade{to{opacity:1}}
+.spinner{width:56px;height:56px;margin:0 auto 20px;border-radius:50%;border:3px solid rgba(255,255,255,.08);border-top-color:#7aa2f7;animation:spin 1s linear infinite}
+@keyframes spin{to{transform:rotate(360deg)}}
+h1{font-size:18px;font-weight:600;margin:0 0 8px;letter-spacing:.2px}
+p{margin:0;color:#9aa1a8;font-size:14px;line-height:1.5}
+.dots::after{display:inline-block;width:1.2em;text-align:left;animation:dots 1.4s steps(4,end) infinite;content:""}
+@keyframes dots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}100%{content:""}}
+.error h1{color:#f7768e}
+.actions{margin-top:20px;display:flex;gap:8px;justify-content:center}
+.btn{padding:10px 16px;border:0;border-radius:10px;cursor:pointer;font:inherit;font-weight:600;background:#7aa2f7;color:#0b0d10}
+.btn.secondary{background:transparent;color:#9aa1a8;border:1px solid rgba(255,255,255,.12)}
+.hidden{display:none}
+@media(prefers-color-scheme:light){body{background:#f5f6f8;color:#1a1d22}.spinner{border-color:rgba(0,0,0,.08);border-top-color:#3b82f6}.btn{background:#3b82f6;color:white}p{color:#52606b}}
+''';
// Inline script for the loader shell. Reads window.__TAB_ID__ and
// window.__CAPTURE_ID__, long-polls /loader/wait, then either redirects
@@ -548,7 +537,7 @@ class CaptureServer {
// service has gone away doesn't busy-loop forever — after the cap the
// loader gives up and shows the error pane with a Retry button (which
// resets the counter via showPending → poll()).
- static const _loaderScript = r'''
+ static const _loaderScript = '''
(function(){
var pending=document.getElementById('pending');
var error=document.getElementById('error');
@@ -606,18 +595,19 @@ class CaptureServer {
String _loaderShellBody({required bool initialError}) {
final pendingClass = initialError ? 'hidden' : '';
final errorClass = initialError ? 'error' : 'error hidden';
- return ''
- ''
- '
Capturing page
'
- '
Saving an offline copy. This usually takes a few seconds.
'
- ''
- ''
- '
Capture failed
'
- '
The page could not be saved. Check the notification for details.
'
- '
'
- ''
- '
'
- '';
+ return '''
+
+
+
Capturing page
+
Saving an offline copy. This usually takes a few seconds.
+
+
+
Capture failed
+
The page could not be saved. Check the notification for details.
+
+
+
+''';
}
}
diff --git a/apps/weblibre/lib/features/web_search/presentation/widgets/search_result_card.dart b/apps/weblibre/lib/features/web_search/presentation/widgets/search_result_card.dart
index ac71ec5d..8a2ce9b2 100644
--- a/apps/weblibre/lib/features/web_search/presentation/widgets/search_result_card.dart
+++ b/apps/weblibre/lib/features/web_search/presentation/widgets/search_result_card.dart
@@ -1,6 +1,5 @@
import 'dart:typed_data';
-import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -211,7 +210,7 @@ class _ContentRow extends StatelessWidget {
/// instances on every state mutation). Equality is driven instead by scalar
/// signatures derived from those collections, which is enough to detect any
/// change a single result card cares about.
-class _FooterState with FastEquatable {
+class _FooterState {
final bool hasOpenSession;
final bool isFetching;
final bool isFetched;
@@ -260,14 +259,26 @@ class _FooterState with FastEquatable {
}
@override
- List