fix lints

This commit is contained in:
Fabian Freund
2026-05-22 19:16:03 +02:00
parent a5974617aa
commit ee99d28379
72 changed files with 244 additions and 274 deletions
+1
View File
@@ -11,6 +11,7 @@ linter:
analyzer: analyzer:
errors: errors:
avoid_redundant_argument_values: ignore
experimental_member_use: ignore experimental_member_use: ignore
exclude: exclude:
- "**.g.dart" - "**.g.dart"
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:ui'; import 'dart:ui';
@@ -193,7 +194,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
inFlight = _fetchAndCacheDdgIcon(url, cacheMissing: cacheMissing) inFlight = _fetchAndCacheDdgIcon(url, cacheMissing: cacheMissing)
.whenComplete(() { .whenComplete(() {
if (identical(_inFlightIconFetches[origin], inFlight)) { if (identical(_inFlightIconFetches[origin], inFlight)) {
_inFlightIconFetches.remove(origin); _inFlightIconFetches.remove(origin)?.ignore();
} }
}); });
@@ -37,7 +37,6 @@ class AccountAuthState with FastEquatable {
// states for the same user but with distinct client instances are still // states for the same user but with distinct client instances are still
// semantically equal — including identityHashCode here would defeat // semantically equal — including identityHashCode here would defeat
// Riverpod's caching by treating every reissued state as different. // Riverpod's caching by treating every reissued state as different.
// ignore: missing_field_in_equatable_props
final SupabaseClient? client; final SupabaseClient? client;
AccountAuthState({ AccountAuthState({
@@ -64,5 +63,6 @@ class AccountAuthState with FastEquatable {
userId, userId,
lastError, lastError,
syncKey, syncKey,
client,
]; ];
} }
@@ -271,7 +271,6 @@ class AccountAuthRepository extends _$AccountAuthRepository {
// Clear the pending code verifier so a late browser callback is rejected. // Clear the pending code verifier so a late browser callback is rejected.
final data = await _store.read(); final data = await _store.read();
// ignore: avoid_redundant_argument_values
await _store.write(data.copyWith(pendingCodeVerifier: null)); await _store.write(data.copyWith(pendingCodeVerifier: null));
state = AsyncData(AccountAuthState()); state = AsyncData(AccountAuthState());
@@ -327,7 +326,6 @@ class AccountAuthRepository extends _$AccountAuthRepository {
displayName: displayName:
(user?['user_metadata'] as Map<String, dynamic>?)?['display_name'] (user?['user_metadata'] as Map<String, dynamic>?)?['display_name']
as String?, as String?,
// ignore: avoid_redundant_argument_values
pendingCodeVerifier: null, pendingCodeVerifier: null,
), ),
); );
@@ -372,9 +370,7 @@ class AccountAuthRepository extends _$AccountAuthRepository {
Future<void> clearSyncKey() async { Future<void> clearSyncKey() async {
final data = await _store.read(); final data = await _store.read();
// ignore: avoid_redundant_argument_values
await _store.write(data.copyWith(syncKey: null)); await _store.write(data.copyWith(syncKey: null));
// ignore: avoid_redundant_argument_values
state = AsyncData(_currentOrEmpty.copyWith(syncKey: null)); state = AsyncData(_currentOrEmpty.copyWith(syncKey: null));
} }
@@ -15,7 +15,7 @@ final webSearchBang = BangData(
searxngApi: false, searxngApi: false,
); );
final webSearchBangKey = BangKey( const webSearchBangKey = BangKey(
group: BangGroup.weblibre, group: BangGroup.weblibre,
trigger: webSearchBangTrigger, trigger: webSearchBangTrigger,
); );
@@ -17,14 +17,12 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'dart:async';
import 'package:fading_scroll/fading_scroll.dart'; import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/domain/providers/search.dart';
import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart'; import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart';
import 'package:weblibre/features/user/domain/providers.dart'; import 'package:weblibre/features/user/domain/providers.dart';
@@ -1033,7 +1033,7 @@ EquatableValue<List<TabListItemEntity>> groupedTabListItems(
final unpinned = children final unpinned = children
.where((c) => !pinnedTabIds.contains(c.row.id)) .where((c) => !pinnedTabIds.contains(c.row.id))
.toList(); .toList();
final cmp = (_GroupedRow a, _GroupedRow b) => int cmp(_GroupedRow a, _GroupedRow b) =>
a.row.orderKey.compareTo(b.row.orderKey); a.row.orderKey.compareTo(b.row.orderKey);
final directionCmp = tabListDirection == TabDirection.newestFirst final directionCmp = tabListDirection == TabDirection.newestFirst
? (_GroupedRow a, _GroupedRow b) => -cmp(a, b) ? (_GroupedRow a, _GroupedRow b) => -cmp(a, b)
@@ -85,12 +85,10 @@ class TabViewFilterController extends _$TabViewFilterController {
} }
void setDateRange(DateTimeRange<DateTime>? range) { void setDateRange(DateTimeRange<DateTime>? range) {
// ignore: avoid_redundant_argument_values
state = state.copyWith(dateRange: range, quickInterval: null); state = state.copyWith(dateRange: range, quickInterval: null);
} }
void setQuickInterval(TabQuickInterval? interval) { void setQuickInterval(TabQuickInterval? interval) {
// ignore: avoid_redundant_argument_values
state = state.copyWith(quickInterval: interval, dateRange: null); state = state.copyWith(quickInterval: interval, dateRange: null);
} }
@@ -1194,13 +1194,11 @@ class _Browser extends HookConsumerWidget {
switch (promptOnBackBehavior) { switch (promptOnBackBehavior) {
case BackgroundAppTabBackPromptBehavior(): case BackgroundAppTabBackPromptBehavior():
await moveToBackground(); await moveToBackground();
break;
case ReturnToSearchTabBackPromptBehavior(:final tabType): case ReturnToSearchTabBackPromptBehavior(:final tabType):
ref ref
.read(searchAutofocusSuppressionProvider.notifier) .read(searchAutofocusSuppressionProvider.notifier)
.suppressNext(); .suppressNext();
await SearchRoute(tabType: tabType).push(context); await SearchRoute(tabType: tabType).push(context);
break;
} }
return true; return true;
@@ -219,7 +219,7 @@ List<String> _orderedIdsForStorageAnchors(
required String movingPartitionRootId, required String movingPartitionRootId,
required bool sortPinnedFirst, required bool sortPinnedFirst,
}) { }) {
var storageOrderedIds = tabListDirection == TabDirection.newestFirst final storageOrderedIds = tabListDirection == TabDirection.newestFirst
// Rendering flips root group order for newest-first; convert the // Rendering flips root group order for newest-first; convert the
// display order back to storage order before choosing anchors. // display order back to storage order before choosing anchors.
? orderedTabIds.reversed.toList() ? orderedTabIds.reversed.toList()
@@ -580,11 +580,13 @@ class QuickTabSwitcher extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final context = activeItemKey.value.currentContext; final context = activeItemKey.value.currentContext;
if (context != null) { if (context != null) {
unawaited(
Scrollable.ensureVisible( Scrollable.ensureVisible(
context, context,
alignment: 0.5, alignment: 0.5,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
),
); );
} else if (chipScrollController.hasClients) { } else if (chipScrollController.hasClients) {
final activeIndex = availableItems.indexWhere( final activeIndex = availableItems.indexWhere(
@@ -602,11 +604,13 @@ class QuickTabSwitcher extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final retryContext = activeItemKey.value.currentContext; final retryContext = activeItemKey.value.currentContext;
if (retryContext != null) { if (retryContext != null) {
unawaited(
Scrollable.ensureVisible( Scrollable.ensureVisible(
retryContext, retryContext,
alignment: 0.5, alignment: 0.5,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
),
); );
} }
}); });
@@ -717,8 +721,6 @@ class QuickTabSwitcherView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appColors = AppColors.of(context); final appColors = AppColors.of(context);
final colorScheme = Theme.of(context).colorScheme;
if (availableItems.isEmpty) { if (availableItems.isEmpty) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@@ -27,8 +27,8 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:weblibre/core/logger.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/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/geckoview/features/tabs/utils/setting_groups_serializer.dart';
import 'package:weblibre/features/user/data/providers.dart'; import 'package:weblibre/features/user/data/providers.dart';
@@ -107,7 +107,7 @@ Future<bool> installCurrentWebApp(
Ref ref, { Ref ref, {
String? overrideName, String? overrideName,
String? contextId, String? contextId,
}) async { }) {
final selectedTabId = ref.read(selectedTabProvider); final selectedTabId = ref.read(selectedTabProvider);
if (selectedTabId == null) { if (selectedTabId == null) {
@@ -156,7 +156,7 @@ Future<bool> installBasicShortcut(
Ref ref, { Ref ref, {
String? overrideName, String? overrideName,
String? contextId, String? contextId,
}) async { }) {
final selectedTabId = ref.read(selectedTabProvider); final selectedTabId = ref.read(selectedTabProvider);
if (selectedTabId == null) { if (selectedTabId == null) {
@@ -801,8 +801,8 @@ class _WebSearchOptionsRow extends StatelessWidget {
controller: controller, controller: controller,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row( child: const Row(
children: const [ children: [
WebSearchStatusChip(), WebSearchStatusChip(),
RouteThroughTorToggle(), RouteThroughTorToggle(),
SizedBox(width: 8), SizedBox(width: 8),
@@ -60,9 +60,7 @@ class FeedSearch extends HookConsumerWidget {
.read(articleSearchProvider(null).notifier) .read(articleSearchProvider(null).notifier)
.search( .search(
searchTextNotifier.value.text, searchTextNotifier.value.text,
// ignore: avoid_redundant_argument_values dont break things
matchPrefix: _matchPrefix, matchPrefix: _matchPrefix,
// ignore: avoid_redundant_argument_values dont break things
matchSuffix: _matchSuffix, matchSuffix: _matchSuffix,
); );
}, },
@@ -123,9 +123,7 @@ class TabSearch extends HookConsumerWidget {
) )
.addQuery( .addQuery(
searchTextListenable.value.text, searchTextListenable.value.text,
// ignore: avoid_redundant_argument_values dont break things
matchPrefix: _matchPrefix, matchPrefix: _matchPrefix,
// ignore: avoid_redundant_argument_values dont break things
matchSuffix: _matchSuffix, matchSuffix: _matchSuffix,
); );
} }
@@ -18,6 +18,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.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'), title: const Text('Change Color'),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
openColorPicker(); unawaited(openColorPicker());
}, },
), ),
ListTile( ListTile(
@@ -189,7 +191,7 @@ class ContainerEditScreen extends HookConsumerWidget {
title: const Text('Change Icon'), title: const Text('Change Icon'),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
openIconPicker(); unawaited(openIconPicker());
}, },
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -105,7 +105,6 @@ class ContainerListScreen extends HookConsumerWidget {
.read(selectedContainerProvider.notifier) .read(selectedContainerProvider.notifier)
.clearContainer() .clearContainer()
: () => setSelectedContainer(container), : () => setSelectedContainer(container),
// onDelete: () => repository.deleteContainer(container.id),
), ),
); );
}, },
@@ -168,7 +167,6 @@ class _ContainerCard extends HookConsumerWidget {
required this.isSelected, required this.isSelected,
required this.onTap, required this.onTap,
required this.onSelect, required this.onSelect,
this.onDelete,
}); });
final ContainerDataWithCount container; final ContainerDataWithCount container;
@@ -176,7 +174,6 @@ class _ContainerCard extends HookConsumerWidget {
final bool isSelected; final bool isSelected;
final VoidCallback onTap; final VoidCallback onTap;
final VoidCallback onSelect; final VoidCallback onSelect;
final VoidCallback? onDelete;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -299,15 +296,6 @@ class _ContainerCard extends HookConsumerWidget {
else else
const SizedBox.shrink(), const SizedBox.shrink(),
const Spacer(), 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( FilledButton.tonalIcon(
onPressed: onSelect, onPressed: onSelect,
icon: Icon(isSelected ? Icons.close : Icons.check), icon: Icon(isSelected ? Icons.close : Icons.check),
@@ -20,9 +20,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; 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:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/core/routing/routes.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/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/entities/container_selection_result.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/entities/container_selection_result.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/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.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_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/presentation/widgets/tab_drag_container_target.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart'; import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
@@ -303,11 +302,13 @@ class ContainerChips extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final activeContext = activeItemKey.value.currentContext; final activeContext = activeItemKey.value.currentContext;
if (activeContext != null) { if (activeContext != null) {
unawaited(
Scrollable.ensureVisible( Scrollable.ensureVisible(
activeContext, activeContext,
alignment: 0.5, alignment: 0.5,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
),
); );
return; return;
} }
@@ -336,11 +337,13 @@ class ContainerChips extends HookConsumerWidget {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final retryContext = activeItemKey.value.currentContext; final retryContext = activeItemKey.value.currentContext;
if (retryContext != null) { if (retryContext != null) {
unawaited(
Scrollable.ensureVisible( Scrollable.ensureVisible(
retryContext, retryContext,
alignment: 0.5, alignment: 0.5,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
),
); );
} }
}); });
@@ -17,9 +17,9 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/design/app_colors.dart';
@@ -18,7 +18,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/proxy/data/models/singbox_proxy_profile.dart'; import 'package:weblibre/features/proxy/data/models/singbox_proxy_profile.dart';
@@ -18,7 +18,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/uuid.dart'; import 'package:weblibre/core/uuid.dart';
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_credentials.dart'; import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_credentials.dart';
@@ -127,7 +127,7 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
Future<SingboxProxyRuntimeState> startProfile( Future<SingboxProxyRuntimeState> startProfile(
String profileId, { String profileId, {
SingboxProxyRuntimeOptions? options, SingboxProxyRuntimeOptions? options,
}) async { }) {
return _lock.synchronized(() async { return _lock.synchronized(() async {
final currentState = await _stateSnapshotUnlocked(); final currentState = await _stateSnapshotUnlocked();
final activeProfileIds = _activeProfileIds(currentState); final activeProfileIds = _activeProfileIds(currentState);
@@ -320,7 +320,7 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
final profileMap = {for (final profile in profiles) profile.id: profile}; final profileMap = {for (final profile in profiles) profile.id: profile};
return Future.wait( return Future.wait(
profileIds.map((profileId) async { profileIds.map((profileId) {
final profile = profileMap[profileId]; final profile = profileMap[profileId];
if (profile == null) { if (profile == null) {
throw StateError('Unknown sing-box proxy profile: $profileId'); throw StateError('Unknown sing-box proxy profile: $profileId');
@@ -340,15 +340,15 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
} }
@override @override
Future<SingboxProxyRuntimeState> build() async { Future<SingboxProxyRuntimeState> build() {
final plugin = ref.watch(singboxProxyClientProvider); final plugin = ref.watch(singboxProxyClientProvider);
final stateSubscription = plugin.stateStream.listen((nextState) { final stateSubscription = plugin.stateStream.listen((nextState) {
state = AsyncData(nextState); state = AsyncData(nextState);
}); });
ref.onDispose(() async { ref.onDispose(() {
await stateSubscription.cancel(); unawaited(stateSubscription.cancel());
await plugin.dispose(); unawaited(plugin.dispose());
}); });
return plugin.getState(); return plugin.getState();
@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -104,13 +106,15 @@ class SingboxProxyEndpointSync extends _$SingboxProxyEndpointSync {
(previous, next) { (previous, next) {
final runtimeState = next.value; final runtimeState = next.value;
if (runtimeState == null) return; if (runtimeState == null) return;
unawaited(
_sync(runtimeState).catchError((Object error, StackTrace stackTrace) { _sync(runtimeState).catchError((Object error, StackTrace stackTrace) {
logger.e( logger.e(
'Failed to sync sing-box proxy endpoints to Gecko', 'Failed to sync sing-box proxy endpoints to Gecko',
error: error, error: error,
stackTrace: stackTrace, stackTrace: stackTrace,
); );
}); }),
);
}, },
); );
} }
@@ -17,25 +17,23 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
abstract final class SearchBackendConfig { /// Clearnet origin used when the user has not opted to route search through
/// Clearnet origin used when the user has not opted to route search through /// Tor. Must be a normal HTTPS URL (or HTTP for dev).
/// Tor. Must be a normal HTTPS URL (or HTTP for dev). const searchBackendOrigin = String.fromEnvironment(
static const searchBackendOrigin = String.fromEnvironment(
'SEARCH_BACKEND_ORIGIN', 'SEARCH_BACKEND_ORIGIN',
defaultValue: 'https://search.weblibre.eu', defaultValue: 'https://search.weblibre.eu',
); );
/// Origin used when the user opts to route search through Tor. Should be /// Origin used when the user opts to route search through Tor. Should be
/// the WebLibre search service's onion address so the Tor circuit /// the WebLibre search service's onion address so the Tor circuit
/// terminates inside the Tor network instead of exiting back to the /// terminates inside the Tor network instead of exiting back to the
/// clearnet. Falls back to the clearnet origin when no onion address is /// clearnet. Falls back to the clearnet origin when no onion address is
/// configured at build time. /// configured at build time.
static const searchBackendOriginTor = String.fromEnvironment( const searchBackendOriginTor = String.fromEnvironment(
'SEARCH_BACKEND_ORIGIN_TOR', 'SEARCH_BACKEND_ORIGIN_TOR',
defaultValue: defaultValue:
'http://eyipgwt32zaejr2xwblaswp2ur4qikapofunbqus5dklvf7jxkncirad.onion', 'http://eyipgwt32zaejr2xwblaswp2ur4qikapofunbqus5dklvf7jxkncirad.onion',
); );
static Uri get originUri => Uri.parse(searchBackendOrigin); Uri get searchBackendOriginUri => Uri.parse(searchBackendOrigin);
static Uri get torOriginUri => Uri.parse(searchBackendOriginTor); Uri get searchBackendTorOriginUri => Uri.parse(searchBackendOriginTor);
}
@@ -33,9 +33,7 @@ BackendEndpoints searchBackendEndpoints(Ref ref) {
webSearchSettingsControllerProvider.select((s) => s.routeThroughTor), webSearchSettingsControllerProvider.select((s) => s.routeThroughTor),
); );
return BackendEndpoints.fromOrigin( return BackendEndpoints.fromOrigin(
routeThroughTor routeThroughTor ? searchBackendTorOriginUri : searchBackendOriginUri,
? SearchBackendConfig.torOriginUri
: SearchBackendConfig.originUri,
); );
} }
@@ -99,8 +99,8 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
pinned: true, pinned: true,
delegate: _ToolbarPreviewDelegate(configs: configs.value), delegate: _ToolbarPreviewDelegate(configs: configs.value),
), ),
SliverPadding( const SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), padding: EdgeInsets.fromLTRB(16, 20, 16, 0),
sliver: SliverToBoxAdapter(child: _SectionLabel(label: 'Enabled')), sliver: SliverToBoxAdapter(child: _SectionLabel(label: 'Enabled')),
), ),
if (visibleConfigs.isEmpty) if (visibleConfigs.isEmpty)
@@ -137,8 +137,8 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
); );
}, },
), ),
SliverPadding( const SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), padding: EdgeInsets.fromLTRB(16, 20, 16, 0),
sliver: SliverToBoxAdapter(child: _SectionLabel(label: 'Disabled')), sliver: SliverToBoxAdapter(child: _SectionLabel(label: 'Disabled')),
), ),
if (hiddenConfigs.isEmpty) if (hiddenConfigs.isEmpty)
@@ -25,7 +25,6 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_addon.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'; import 'package:weblibre/features/settings/presentation/widgets/settings_detail.dart';
const List<SettingsSectionDefinition> extensionsSettingsSections = [ const List<SettingsSectionDefinition> extensionsSettingsSections = [
@@ -17,7 +17,6 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart' import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'
@@ -41,6 +41,7 @@ const _alwaysAllowPackageExtra = 'eu.weblibre.gatekeeper.always_allow_package';
StreamTransformer<Intent, ReceivedIntentParameter> StreamTransformer<Intent, ReceivedIntentParameter>
_buildSharingIntentTransformer( _buildSharingIntentTransformer(
IntentGatekeeper gatekeeper, IntentGatekeeper gatekeeper,
GeneralSettingsRepository settingsRepository,
) => StreamTransformer<Intent, ReceivedIntentParameter>.fromHandlers( ) => StreamTransformer<Intent, ReceivedIntentParameter>.fromHandlers(
handleData: (intent, sink) async { handleData: (intent, sink) async {
if (_extractAccountCallback(intent) != null) { if (_extractAccountCallback(intent) != null) {
@@ -53,9 +54,7 @@ _buildSharingIntentTransformer(
final alwaysAllowPackage = final alwaysAllowPackage =
intent.extra[_alwaysAllowPackageExtra] as String?; intent.extra[_alwaysAllowPackageExtra] as String?;
if (alwaysAllowPackage != null) { if (alwaysAllowPackage != null) {
await gatekeeper.ref await settingsRepository.updateSettings(
.read(generalSettingsRepositoryProvider.notifier)
.updateSettings(
(current) => current.copyWith.externalAppIntentPolicies({ (current) => current.copyWith.externalAppIntentPolicies({
...current.externalAppIntentPolicies, ...current.externalAppIntentPolicies,
alwaysAllowPackage: IntentSourcePolicy.allow, alwaysAllowPackage: IntentSourcePolicy.allow,
@@ -197,10 +196,13 @@ Raw<Stream<T>> _consumeIntents<T>(
Raw<Stream<ReceivedIntentParameter>> sharingIntentStream(Ref ref) { Raw<Stream<ReceivedIntentParameter>> sharingIntentStream(Ref ref) {
final receiver = ref.watch(intentReceiverProvider); final receiver = ref.watch(intentReceiverProvider);
final gatekeeper = ref.watch(intentGatekeeperProvider.notifier); final gatekeeper = ref.watch(intentGatekeeperProvider.notifier);
final settingsRepository = ref.watch(
generalSettingsRepositoryProvider.notifier,
);
return _consumeIntents( return _consumeIntents(
ref, ref,
receiver, receiver,
_buildSharingIntentTransformer(gatekeeper), _buildSharingIntentTransformer(gatekeeper, settingsRepository),
); );
} }
@@ -1,5 +1,3 @@
// ignore_for_file: avoid_redundant_argument_values
/* /*
* Copyright (c) 2024-2026 Fabian Freund. * Copyright (c) 2024-2026 Fabian Freund.
* *
@@ -315,21 +315,15 @@ class SyncRepository extends _$SyncRepository {
_syncStartedSub = syncStateService.syncStartedEvents.listen((_) { _syncStartedSub = syncStateService.syncStartedEvents.listen((_) {
_update( _update(
(s) => s.copyWith( (s) =>
lastSyncEvent: SyncEvent.started, s.copyWith(lastSyncEvent: SyncEvent.started, lastSyncError: null),
// ignore: avoid_redundant_argument_values
lastSyncError: null,
),
); );
}); });
_syncCompletedSub = syncStateService.syncCompletedEvents.listen((_) { _syncCompletedSub = syncStateService.syncCompletedEvents.listen((_) {
_update( _update(
(s) => s.copyWith( (s) =>
lastSyncEvent: SyncEvent.completed, s.copyWith(lastSyncEvent: SyncEvent.completed, lastSyncError: null),
// ignore: avoid_redundant_argument_values
lastSyncError: null,
),
); );
unawaited(Future.wait([_refreshTabs(), _refreshDevices()])); unawaited(Future.wait([_refreshTabs(), _refreshDevices()]));
}); });
@@ -19,10 +19,10 @@
*/ */
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:weblibre/data/database/extensions/database_table_size.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/daos/cache.drift.dart';
import 'package:weblibre/features/user/data/database/database.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/database/definitions.drift.dart';
import 'package:weblibre/features/user/data/icon_cache_marker.dart';
@DriftAccessor() @DriftAccessor()
class CacheDao extends DatabaseAccessor<UserDatabase> with $CacheDaoMixin { class CacheDao extends DatabaseAccessor<UserDatabase> with $CacheDaoMixin {
@@ -67,7 +67,7 @@ class SearchTokensDao extends DatabaseAccessor<UserDatabase> {
/// Release any reservation older than [maxAge] — covers crashes mid-flight. /// Release any reservation older than [maxAge] — covers crashes mid-flight.
/// Returns the number of rows released. /// Returns the number of rows released.
Future<int> releaseStaleReservations(Duration maxAge) async { Future<int> releaseStaleReservations(Duration maxAge) {
final cutoff = DateTime.now().subtract(maxAge); final cutoff = DateTime.now().subtract(maxAge);
return (update(db.searchTokens)..where( return (update(db.searchTokens)..where(
(t) => (t) =>
@@ -261,7 +261,7 @@ class UBlockAssetsRegistry {
return count; return count;
} }
static UBlockAssetsRegistry fromJson(Map<String, dynamic> json) { factory UBlockAssetsRegistry.fromJson(Map<String, dynamic> json) {
final entries = <String, UBlockAssetEntry>{}; final entries = <String, UBlockAssetEntry>{};
for (final entry in json.entries) { for (final entry in json.entries) {
if (entry.value is Map<String, dynamic>) { if (entry.value is Map<String, dynamic>) {
@@ -17,16 +17,15 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
library;
/// Curated picker options for the web-search language and country selectors. // Curated picker options for the web-search language and country selectors.
/// //
/// Codes intersect what Brave (`search_lang`/`country`) and Mojeek // Codes intersect what Brave (`search_lang`/`country`) and Mojeek
/// (`lb`/`rb`) accept; English display names so the menu doesn't need a // (`lb`/`rb`) accept; English display names so the menu doesn't need a
/// per-locale translation pass. The protocol carries just the primary // per-locale translation pass. The protocol carries just the primary
/// ISO 639-1 / ISO 3166-1 alpha-2 codes — region qualifiers (`en-gb`, // ISO 639-1 / ISO 3166-1 alpha-2 codes — region qualifiers (`en-gb`,
/// `pt-br`, etc.) are reconstructed server-side from the language+country // `pt-br`, etc.) are reconstructed server-side from the language+country
/// pair. // pair.
class LanguageOption { class LanguageOption {
final String code; // ISO 639-1 final String code; // ISO 639-1
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart' import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'
as fmc; as fmc;
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:search_protocol/search_protocol.dart';
import 'package:search_client/search_client.dart'; import 'package:search_client/search_client.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.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/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/controllers/search_token_issuance_controller.dart';
import 'package:weblibre/features/search_credits/domain/providers.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/search_credits/domain/repositories/search_token_stash_repository.dart';
import 'package:weblibre/features/user/domain/repositories/cache.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_artifact_downloader.dart';
import 'package:weblibre/features/web_search/domain/services/capture_server.dart'; import 'package:weblibre/features/web_search/domain/services/capture_server.dart';
import 'package:weblibre/features/web_search/domain/services/sandbox_capture_store.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) @Riverpod(keepAlive: true)
Stream<SandboxCaptureError> sandboxCaptureErrors(Ref ref) { Stream<SandboxCaptureError> sandboxCaptureErrors(Ref ref) {
final ctrl = ref.watch(sandboxCaptureControllerProvider.notifier); final ctrl = ref.watch(sandboxCaptureControllerProvider.notifier);
return ctrl.errors; return ctrl.errorStream();
} }
/// Orchestrates sandbox capture browsing: /// Orchestrates sandbox capture browsing:
@@ -148,10 +147,7 @@ class SandboxCaptureController extends _$SandboxCaptureController {
static const _defaultMethod = 'singlefile'; static const _defaultMethod = 'singlefile';
static const _defaultVariant = 'balanced'; static const _defaultVariant = 'balanced';
StreamSubscription<List<CaptureTabData>>? _captureTabSub; Stream<SandboxCaptureError> errorStream() => _errors.stream;
StreamSubscription<RetryRequest>? _retrySub;
Stream<SandboxCaptureError> get errors => _errors.stream;
@override @override
void build() { void build() {
@@ -159,9 +155,9 @@ class SandboxCaptureController extends _$SandboxCaptureController {
fmc.SandboxCaptureHostEvents.setUp(_hostEvents); fmc.SandboxCaptureHostEvents.setUp(_hostEvents);
final dao = ref.read(tabDatabaseProvider).captureTabDao; final dao = ref.read(tabDatabaseProvider).captureTabDao;
_captureTabSub = dao.watchAll().listen(_onCaptureTabChange); final captureTabSub = dao.watchAll().listen(_onCaptureTabChange);
_retrySub = ref final retrySub = ref
.read(captureServerProvider) .read(captureServerProvider)
.retryRequests .retryRequests
.listen(_onRetryRequest); .listen(_onRetryRequest);
@@ -173,10 +169,8 @@ class SandboxCaptureController extends _$SandboxCaptureController {
// stream — otherwise an in-flight Pigeon event or DAO emit could // stream — otherwise an in-flight Pigeon event or DAO emit could
// race with handler cleanup, or _markFailed could try to add to a // race with handler cleanup, or _markFailed could try to add to a
// closed _errors sink. // closed _errors sink.
final captureTabSub = _captureTabSub; unawaited(captureTabSub.cancel());
final retrySub = _retrySub; unawaited(retrySub.cancel());
if (captureTabSub != null) unawaited(captureTabSub.cancel());
if (retrySub != null) unawaited(retrySub.cancel());
fmc.SandboxCaptureHostEvents.setUp(null); fmc.SandboxCaptureHostEvents.setUp(null);
_hostEvents.controller = null; _hostEvents.controller = null;
unawaited(_errors.close()); unawaited(_errors.close());
@@ -4,7 +4,6 @@ import 'dart:typed_data';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:search_protocol/search_protocol.dart';
import 'package:search_client/search_client.dart'; import 'package:search_client/search_client.dart';
import 'package:weblibre/domain/services/generic_website.dart'; import 'package:weblibre/domain/services/generic_website.dart';
import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart'; import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart';
@@ -1226,7 +1225,10 @@ class WebSearchScrollOffset extends _$WebSearchScrollOffset {
@override @override
double build() => 0; double build() => 0;
void update(double offset) => state = offset; void update(double offset) {
if (state == offset) return;
state = offset;
}
void reset() => state = 0; void reset() => state = 0;
} }
@@ -5,7 +5,6 @@ import 'dart:ui';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:search_protocol/search_protocol.dart';
import 'package:search_client/search_client.dart'; import 'package:search_client/search_client.dart';
import 'package:weblibre/core/filesystem.dart'; import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/features/search_credits/domain/providers.dart'; import 'package:weblibre/features/search_credits/domain/providers.dart';
@@ -361,14 +361,14 @@ class CaptureServer {
final tabIdJson = jsonEncode(tabId); final tabIdJson = jsonEncode(tabId);
final captureIdJson = jsonEncode(captureId); final captureIdJson = jsonEncode(captureId);
final html = final html =
'<!doctype html><html><head><meta charset="utf-8">' '''
'<meta name="viewport" content="width=device-width,initial-scale=1">' <!doctype html><html><head><meta charset="utf-8">
'<title>Capturing…</title><style>$_loaderStyles</style>' <meta name="viewport" content="width=device-width,initial-scale=1">
'</head><body>$body' <title>Capturing</title><style>$_loaderStyles</style>
'<script>window.__TAB_ID__=$tabIdJson;' </head><body>$body
'window.__CAPTURE_ID__=$captureIdJson;</script>' <script>window.__TAB_ID__=$tabIdJson;window.__CAPTURE_ID__=$captureIdJson;</script>
'<script>$_loaderScript</script>' <script>$_loaderScript</script>
'</body></html>'; </body></html>''';
request.response.write(html); request.response.write(html);
await request.response.close(); await request.response.close();
} }
@@ -510,35 +510,24 @@ class CaptureServer {
"base-uri 'none'; " "base-uri 'none'; "
"form-action 'self'"; "form-action 'self'";
static const _loaderStyles = static const _loaderStyles = '''
'html,body{height:100%}' html,body{height:100%}
'body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,' 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}
'sans-serif;display:flex;align-items:center;justify-content:center;' main{text-align:center;max-width:480px;padding:32px;opacity:0;animation:fade .35s ease-out forwards}
'min-height:100vh;margin:0;background:#0b0d10;color:#e6e8eb}' @keyframes fade{to{opacity:1}}
'main{text-align:center;max-width:480px;padding:32px;opacity:0;' .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}
'animation:fade .35s ease-out forwards}' @keyframes spin{to{transform:rotate(360deg)}}
'@keyframes fade{to{opacity:1}}' h1{font-size:18px;font-weight:600;margin:0 0 8px;letter-spacing:.2px}
'.spinner{width:56px;height:56px;margin:0 auto 20px;border-radius:50%;' p{margin:0;color:#9aa1a8;font-size:14px;line-height:1.5}
'border:3px solid rgba(255,255,255,.08);border-top-color:#7aa2f7;' .dots::after{display:inline-block;width:1.2em;text-align:left;animation:dots 1.4s steps(4,end) infinite;content:""}
'animation:spin 1s linear infinite}' @keyframes dots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}100%{content:""}}
'@keyframes spin{to{transform:rotate(360deg)}}' .error h1{color:#f7768e}
'h1{font-size:18px;font-weight:600;margin:0 0 8px;letter-spacing:.2px}' .actions{margin-top:20px;display:flex;gap:8px;justify-content:center}
'p{margin:0;color:#9aa1a8;font-size:14px;line-height:1.5}' .btn{padding:10px 16px;border:0;border-radius:10px;cursor:pointer;font:inherit;font-weight:600;background:#7aa2f7;color:#0b0d10}
'.dots::after{display:inline-block;width:1.2em;text-align:left;' .btn.secondary{background:transparent;color:#9aa1a8;border:1px solid rgba(255,255,255,.12)}
'animation:dots 1.4s steps(4,end) infinite;content:""}' .hidden{display:none}
'@keyframes dots{0%{content:""}25%{content:"."}50%{content:".."}' @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}}
'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 // Inline script for the loader shell. Reads window.__TAB_ID__ and
// window.__CAPTURE_ID__, long-polls /loader/wait, then either redirects // 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 // 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 // loader gives up and shows the error pane with a Retry button (which
// resets the counter via showPending → poll()). // resets the counter via showPending → poll()).
static const _loaderScript = r''' static const _loaderScript = '''
(function(){ (function(){
var pending=document.getElementById('pending'); var pending=document.getElementById('pending');
var error=document.getElementById('error'); var error=document.getElementById('error');
@@ -606,18 +595,19 @@ class CaptureServer {
String _loaderShellBody({required bool initialError}) { String _loaderShellBody({required bool initialError}) {
final pendingClass = initialError ? 'hidden' : ''; final pendingClass = initialError ? 'hidden' : '';
final errorClass = initialError ? 'error' : 'error hidden'; final errorClass = initialError ? 'error' : 'error hidden';
return '<main id="pending" class="$pendingClass">' return '''
'<div class="spinner"></div>' <main id="pending" class="$pendingClass">
'<h1>Capturing page<span class="dots"></span></h1>' <div class="spinner"></div>
'<p>Saving an offline copy. This usually takes a few seconds.</p>' <h1>Capturing page<span class="dots"></span></h1>
'</main>' <p>Saving an offline copy. This usually takes a few seconds.</p>
'<main id="error" class="$errorClass">' </main>
'<h1>Capture failed</h1>' <main id="error" class="$errorClass">
'<p>The page could not be saved. Check the notification for details.</p>' <h1>Capture failed</h1>
'<div class="actions">' <p>The page could not be saved. Check the notification for details.</p>
'<button id="retry" class="btn" type="button">Retry</button>' <div class="actions">
'</div>' <button id="retry" class="btn" type="button">Retry</button>
'</main>'; </div>
</main>''';
} }
} }
@@ -1,6 +1,5 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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 /// instances on every state mutation). Equality is driven instead by scalar
/// signatures derived from those collections, which is enough to detect any /// signatures derived from those collections, which is enough to detect any
/// change a single result card cares about. /// change a single result card cares about.
class _FooterState with FastEquatable { class _FooterState {
final bool hasOpenSession; final bool hasOpenSession;
final bool isFetching; final bool isFetching;
final bool isFetched; final bool isFetched;
@@ -260,14 +259,26 @@ class _FooterState with FastEquatable {
} }
@override @override
List<Object?> get hashParameters => [ bool operator ==(Object other) {
return identical(this, other) ||
other is _FooterState &&
hasOpenSession == other.hasOpenSession &&
isFetching == other.isFetching &&
isFetched == other.isFetched &&
fetchError == other.fetchError &&
_capturingSignature == other._capturingSignature &&
_capturesSignature == other._capturesSignature;
}
@override
int get hashCode => Object.hash(
hasOpenSession, hasOpenSession,
isFetching, isFetching,
isFetched, isFetched,
fetchError, fetchError,
_capturingSignature, _capturingSignature,
_capturesSignature, _capturesSignature,
]; );
} }
class _FetchFooter extends ConsumerWidget { class _FetchFooter extends ConsumerWidget {
@@ -151,10 +151,10 @@ bool _languageMatchesQuery(String resultLanguage, String? queryTag) {
final result = resultLanguage final result = resultLanguage
.trim() .trim()
.toLowerCase() .toLowerCase()
.split(RegExp(r'[-_]')) .split(RegExp('[-_]'))
.first; .first;
final query = queryTag.trim().toLowerCase().split(RegExp(r'[-_]')).first; final query = queryTag.trim().toLowerCase().split(RegExp('[-_]')).first;
if (result.isEmpty || query.isEmpty) return false; if (result.isEmpty || query.isEmpty) return false;
+10 -10
View File
@@ -16,7 +16,6 @@ dependencies:
country_codes: ^3.3.0 country_codes: ^3.3.0
country_flags: ^4.1.2 country_flags: ^4.1.2
crypto: ^3.0.7 crypto: ^3.0.7
xxh3: ^1.2.0
cryptography_flutter: ^2.3.4 cryptography_flutter: ^2.3.4
device_info_plus: ^12.4.0 device_info_plus: ^12.4.0
drift: ^2.31.0 drift: ^2.31.0
@@ -31,15 +30,15 @@ dependencies:
sdk: flutter sdk: flutter
flutter_auto_size_text: ^5.0.0 flutter_auto_size_text: ^5.0.0
flutter_hooks: ^0.21.3+1 flutter_hooks: ^0.21.3+1
flutter_secure_storage: ^10.1.0
flutter_markdown: ^0.7.7+1 flutter_markdown: ^0.7.7+1
flutter_material_design_icons: ^3.1.0+7447 flutter_material_design_icons: ^3.1.0+7447
flutter_mozilla_components: flutter_mozilla_components:
path: ../../packages/flutter_mozilla_components path: ../../packages/flutter_mozilla_components
flutter_reorderable_grid_view: ^5.6.0 flutter_reorderable_grid_view: ^5.6.0
flutter_slidable: ^4.0.3 flutter_secure_storage: ^10.1.0
flutter_singbox_proxy: flutter_singbox_proxy:
path: ../../packages/flutter_singbox_proxy path: ../../packages/flutter_singbox_proxy
flutter_slidable: ^4.0.3
flutter_svg: ^2.3.0 flutter_svg: ^2.3.0
flutter_tor: flutter_tor:
path: ../../packages/flutter_tor path: ../../packages/flutter_tor
@@ -67,13 +66,9 @@ dependencies:
path: ^1.9.1 path: ^1.9.1
path_provider: ^2.1.5 path_provider: ^2.1.5
permission_handler: ^12.0.1 permission_handler: ^12.0.1
pretty_qr_code: ^3.6.0
privacypass_client: privacypass_client:
path: ../../../weblibre_account/packages/privacypass_client path: ../../../weblibre_account/packages/privacypass_client
search_client:
path: ../../../weblibre_account/packages/search_client
search_protocol:
path: ../../../weblibre_account/packages/search_protocol
pretty_qr_code: ^3.6.0
qr_code_scanner_plus: ^2.1.1 qr_code_scanner_plus: ^2.1.1
quick_actions: ^1.1.0 quick_actions: ^1.1.0
riverpod: ^3.2.1 riverpod: ^3.2.1
@@ -82,6 +77,10 @@ dependencies:
rxdart: ^0.28.0 rxdart: ^0.28.0
saf_stream: ^2.0.0 saf_stream: ^2.0.0
saf_util: ^2.1.0 saf_util: ^2.1.0
search_client:
path: ../../../weblibre_account/packages/search_client
search_protocol:
path: ../../../weblibre_account/packages/search_protocol
secure_archive: secure_archive:
git: git:
url: https://github.com/FaFre/secure_archive.git url: https://github.com/FaFre/secure_archive.git
@@ -97,8 +96,8 @@ dependencies:
speech_to_text_dialog: speech_to_text_dialog:
path: ../../packages/speech_to_text_dialog path: ../../packages/speech_to_text_dialog
sqlite3: ^2.9.4 sqlite3: ^2.9.4
supabase: ^2.10.6
sqlite3_flutter_libs: "0.5.41" sqlite3_flutter_libs: "0.5.41"
supabase: ^2.10.6
synchronized: ^3.4.0+1 synchronized: ^3.4.0+1
text_scroll: ^0.2.1 text_scroll: ^0.2.1
timeago: ^3.7.1 timeago: ^3.7.1
@@ -106,8 +105,9 @@ dependencies:
git: git:
url: https://github.com/FaFre/uri-to-file.git url: https://github.com/FaFre/uri-to-file.git
url_launcher: ^6.3.2 url_launcher: ^6.3.2
web_socket_channel: ^3.0.3
uuid: ^4.5.3 uuid: ^4.5.3
web_socket_channel: ^3.0.3
xxh3: ^1.2.0
dev_dependencies: dev_dependencies:
build_runner: ^2.15.0 build_runner: ^2.15.0
@@ -1,12 +1,11 @@
import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
import 'package:drift/drift.dart' show Variable; import 'package:drift/drift.dart' show Variable;
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart'; import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
import 'package:weblibre/domain/services/favicon_resolver.dart'; import 'package:weblibre/domain/services/favicon_resolver.dart';
@@ -222,23 +221,26 @@ final _generatorResult = IconResult(
); );
final _cachedSvgBytes = Uint8List.fromList( final _cachedSvgBytes = Uint8List.fromList(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">' '''
'<rect width="16" height="16" fill="#ff0000"/>' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
'</svg>' <rect width="16" height="16" fill="#ff0000"/>
</svg>'''
.codeUnits, .codeUnits,
); );
final _updatedSvgBytes = Uint8List.fromList( final _updatedSvgBytes = Uint8List.fromList(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">' '''
'<circle cx="8" cy="8" r="8" fill="#0000ff"/>' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
'</svg>' <circle cx="8" cy="8" r="8" fill="#0000ff"/>
</svg>'''
.codeUnits, .codeUnits,
); );
final _generatedSvgBytes = Uint8List.fromList( final _generatedSvgBytes = Uint8List.fromList(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">' '''
'<rect width="16" height="16" rx="3" fill="#5B8DEF"/>' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
'<text x="8" y="11" text-anchor="middle" font-size="8" fill="#fff">W</text>' <rect width="16" height="16" rx="3" fill="#5B8DEF"/>
'</svg>' <text x="8" y="11" text-anchor="middle" font-size="8" fill="#fff">W</text>
</svg>'''
.codeUnits, .codeUnits,
); );
@@ -83,19 +83,21 @@ void main() {
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
await tester.pump(); await tester.pump();
expect(repository.refreshCount, 1); expect(repository.refreshCount(), 1);
}, },
); );
} }
class _FakeSubscriptionRepository extends SubscriptionRepository { class _FakeSubscriptionRepository extends SubscriptionRepository {
int refreshCount = 0; int _refreshCount = 0;
int refreshCount() => _refreshCount;
@override @override
Future<SubscriptionStatus> build() async => SubscriptionStatus.inactive; Future<SubscriptionStatus> build() async => SubscriptionStatus.inactive;
@override @override
Future<void> refresh() async { Future<void> refresh() async {
refreshCount++; _refreshCount++;
} }
} }
@@ -18,8 +18,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// ignore_for_file: avoid_redundant_argument_values
import 'dart:io'; import 'dart:io';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
@@ -12,7 +12,6 @@ import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i5; import 'package:mockito/src/dummies.dart' as _i5;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters // ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references // ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use
@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// ignore_for_file: avoid_redundant_argument_values, avoid_dynamic_calls // ignore_for_file: avoid_dynamic_calls
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@@ -12,7 +12,6 @@ import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i5; import 'package:mockito/src/dummies.dart' as _i5;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters // ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references // ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use
@@ -28,7 +28,7 @@ void main() {
test( test(
'container assignments ensure sing-box profiles are running before syncing', 'container assignments ensure sing-box profiles are running before syncing',
() async { () async {
final profileId = SingboxProxyConnectionId('profile-1'); const profileId = SingboxProxyConnectionId('profile-1');
final assignedContainer = _container( final assignedContainer = _container(
id: 'container-1', id: 'container-1',
contextId: 'context-a', contextId: 'context-a',
@@ -439,7 +439,6 @@ void main() {
UrlCleanerRule( UrlCleanerRule(
name: 'test', name: 'test',
data: UrlCleanerRuleData( data: UrlCleanerRuleData(
// ignore: avoid_redundant_argument_values
completeProvider: false, completeProvider: false,
urlPattern: r'^https?://example\.com', urlPattern: r'^https?://example\.com',
rules: ['track'], rules: ['track'],
@@ -24,7 +24,7 @@ void main() {
color: Colors.blue, color: Colors.blue,
orderKey: 'a', orderKey: 'a',
metadata: ContainerMetadata.withDefaults( metadata: ContainerMetadata.withDefaults(
proxyConnectionId: SingboxProxyConnectionId('missing-proxy'), proxyConnectionId: const SingboxProxyConnectionId('missing-proxy'),
), ),
), ),
), ),
@@ -438,7 +438,7 @@ class _FakeContainerRepository extends ContainerRepository {
class _FakeProxyRoutingSettingsRepository class _FakeProxyRoutingSettingsRepository
extends ProxyRoutingSettingsRepository { extends ProxyRoutingSettingsRepository {
ProxyRoutingSettings? _settings; final ProxyRoutingSettings? _settings;
_FakeProxyRoutingSettingsRepository([this._settings]); _FakeProxyRoutingSettingsRepository([this._settings]);
@@ -127,16 +127,18 @@ void main() {
test('buildGroupedParentTree creates correct hierarchy', () { test('buildGroupedParentTree creates correct hierarchy', () {
final registry = _makeRegistry(); final registry = _makeRegistry();
final tree = registry.buildGroupedParentTree(); final tree = registry.buildGroupedParentTree();
final defaultGroup = tree[UBlockAssetGroup.$default];
final adsGroup = tree[UBlockAssetGroup.ads];
expect(tree, contains(UBlockAssetGroup.$default)); expect(tree, contains(UBlockAssetGroup.$default));
expect(tree[UBlockAssetGroup.$default]!, contains('uBlock filters')); expect(defaultGroup, contains('uBlock filters'));
expect( expect(
tree[UBlockAssetGroup.$default]!['uBlock filters'], defaultGroup?['uBlock filters'],
containsAll(['ublock-filters', 'ublock-privacy']), containsAll(['ublock-filters', 'ublock-privacy']),
); );
expect(tree, contains(UBlockAssetGroup.ads)); expect(tree, contains(UBlockAssetGroup.ads));
expect(tree[UBlockAssetGroup.ads]!, contains(null)); expect(adsGroup, contains(null));
expect(tree[UBlockAssetGroup.ads]![null], contains('easylist')); expect(adsGroup?[null], contains('easylist'));
expect(tree, contains(UBlockAssetGroup.regions)); expect(tree, contains(UBlockAssetGroup.regions));
}); });
@@ -34,7 +34,6 @@ void main() {
); );
test('forces animations off when the app setting is enabled', () { test('forces animations off when the app setting is enabled', () {
// ignore: avoid_redundant_argument_values
const mediaQuery = MediaQueryData(disableAnimations: false); const mediaQuery = MediaQueryData(disableAnimations: false);
final result = applyAppMediaQueryOverrides( final result = applyAppMediaQueryOverrides(
@@ -3,9 +3,9 @@ import 'dart:typed_data';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart'; import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
import 'package:weblibre/domain/services/favicon_resolver.dart'; import 'package:weblibre/domain/services/favicon_resolver.dart';
@@ -128,7 +128,7 @@ void main() {
final class _NeverCalledResolver implements FaviconResolver { final class _NeverCalledResolver implements FaviconResolver {
@override @override
Future<FaviconResolveResult> resolve(Uri url, {int? proxyPort}) async { Future<FaviconResolveResult> resolve(Uri url, {int? proxyPort}) {
throw UnimplementedError('cacheOnly should not hit the resolver'); throw UnimplementedError('cacheOnly should not hit the resolver');
} }
} }
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const ProviderScope(child: MyApp()));
} }
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@@ -14,6 +14,7 @@ dependencies:
flutter_mozilla_components: flutter_mozilla_components:
path: ../ path: ../
hooks_riverpod: ^3.3.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -69,8 +69,8 @@ export 'src/pigeons/gecko.g.dart'
GeckoDeleteBrowsingDataController, GeckoDeleteBrowsingDataController,
GeckoEngineSettings, GeckoEngineSettings,
GeckoFetchResponse, GeckoFetchResponse,
GeckoProxySettings,
GeckoPref, GeckoPref,
GeckoProxySettings,
GeckoPublicSuffixListApi, GeckoPublicSuffixListApi,
GeckoPwaApi, GeckoPwaApi,
GeckoSitePermissionsApi, GeckoSitePermissionsApi,
@@ -6,11 +6,10 @@
// For more information about Flutter integration tests, please see // For more information about Flutter integration tests, please see
// https://flutter.dev/to/integration-testing // https://flutter.dev/to/integration-testing
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const ProviderScope(child: MyApp()));
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@@ -15,6 +15,7 @@ environment:
# the latest version available on pub.dev. To see which dependencies have newer # the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`. # versions available, run `flutter pub outdated`.
dependencies: dependencies:
cupertino_icons: ^1.0.8
flutter: flutter:
sdk: flutter sdk: flutter
@@ -25,24 +26,20 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on # The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version. # the parent directory to use the current plugin's version.
path: ../ path: ../
hooks_riverpod: ^3.3.1
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies: dev_dependencies:
integration_test: flutter_lints: ^6.0.0
sdk: flutter
flutter_test: flutter_test:
sdk: flutter sdk: flutter
integration_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to # The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is # encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@@ -6,9 +6,8 @@
// tree, read text, and verify that the values of widget properties are correct. // tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_singbox_proxy_example/main.dart'; import 'package:flutter_singbox_proxy_example/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('shows proxy runtime status label', (WidgetTester tester) async { testWidgets('shows proxy runtime status label', (WidgetTester tester) async {
@@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'src/singbox_proxy_api.g.dart'; import 'package:flutter_singbox_proxy/src/singbox_proxy_api.g.dart';
export 'src/singbox_proxy_api.g.dart' export 'src/singbox_proxy_api.g.dart'
show show
@@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
test('profile model keeps generic sing-box config boundary', () { test('profile model keeps generic sing-box config boundary', () {
+2 -1
View File
@@ -6,10 +6,11 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_tor/flutter_tor.dart'; import 'package:flutter_tor/flutter_tor.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:socks5_proxy/socks_client.dart'; import 'package:socks5_proxy/socks_client.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const ProviderScope(child: MyApp()));
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@@ -19,6 +19,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter_tor: flutter_tor:
path: ../ path: ../
hooks_riverpod: ^3.3.1
# HTTP client for testing Tor connectivity # HTTP client for testing Tor connectivity
http: ^1.6.0 http: ^1.6.0
@@ -18,6 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import 'package:simple_intent_receiver/simple_intent_receiver.dart'; import 'package:simple_intent_receiver/simple_intent_receiver.dart';
@@ -34,7 +35,7 @@ void main() {
logger.d(data.action); logger.d(data.action);
}); });
runApp(const MyApp()); runApp(const ProviderScope(child: MyApp()));
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@@ -17,6 +17,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
hooks_riverpod: ^3.3.1
logger: ^2.7.0 logger: ^2.7.0
simple_intent_receiver: simple_intent_receiver:
# When depending on this package from a real application you should use: # When depending on this package from a real application you should use:
@@ -1,10 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:speech_to_text_dialog/speech_to_text_dialog.dart'; import 'package:speech_to_text_dialog/speech_to_text_dialog.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const ProviderScope(child: MyApp()));
} }
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@@ -17,6 +17,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
hooks_riverpod: ^3.3.1
speech_to_text_dialog: speech_to_text_dialog:
# When depending on this package from a real application you should use: # When depending on this package from a real application you should use: