fix lints
This commit is contained in:
@@ -11,6 +11,7 @@ linter:
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
avoid_redundant_argument_values: ignore
|
||||
experimental_member_use: ignore
|
||||
exclude:
|
||||
- "**.g.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic>?)?['display_name']
|
||||
as String?,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
pendingCodeVerifier: null,
|
||||
),
|
||||
);
|
||||
@@ -372,9 +370,7 @@ class AccountAuthRepository extends _$AccountAuthRepository {
|
||||
|
||||
Future<void> 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ final webSearchBang = BangData(
|
||||
searxngApi: false,
|
||||
);
|
||||
|
||||
final webSearchBangKey = BangKey(
|
||||
const webSearchBangKey = BangKey(
|
||||
group: BangGroup.weblibre,
|
||||
trigger: webSearchBangTrigger,
|
||||
);
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:async';
|
||||
|
||||
import 'package: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';
|
||||
|
||||
@@ -1033,7 +1033,7 @@ EquatableValue<List<TabListItemEntity>> 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)
|
||||
|
||||
-2
@@ -85,12 +85,10 @@ class TabViewFilterController extends _$TabViewFilterController {
|
||||
}
|
||||
|
||||
void setDateRange(DateTimeRange<DateTime>? 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);
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -219,7 +219,7 @@ List<String> _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()
|
||||
|
||||
+14
-12
@@ -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();
|
||||
}
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ Future<bool> installCurrentWebApp(
|
||||
Ref ref, {
|
||||
String? overrideName,
|
||||
String? contextId,
|
||||
}) async {
|
||||
}) {
|
||||
final selectedTabId = ref.read(selectedTabProvider);
|
||||
|
||||
if (selectedTabId == null) {
|
||||
@@ -156,7 +156,7 @@ Future<bool> installBasicShortcut(
|
||||
Ref ref, {
|
||||
String? overrideName,
|
||||
String? contextId,
|
||||
}) async {
|
||||
}) {
|
||||
final selectedTabId = ref.read(selectedTabProvider);
|
||||
|
||||
if (selectedTabId == null) {
|
||||
|
||||
+2
-2
@@ -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),
|
||||
|
||||
-2
@@ -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,
|
||||
);
|
||||
},
|
||||
|
||||
-2
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
+4
-2
@@ -18,6 +18,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
@@ -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),
|
||||
|
||||
-12
@@ -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),
|
||||
|
||||
-2
@@ -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';
|
||||
|
||||
+14
-11
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package: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';
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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';
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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';
|
||||
|
||||
@@ -127,7 +127,7 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository {
|
||||
Future<SingboxProxyRuntimeState> 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<SingboxProxyRuntimeState> build() async {
|
||||
Future<SingboxProxyRuntimeState> 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();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_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,
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,25 +17,23 @@
|
||||
* 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/>.
|
||||
*/
|
||||
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);
|
||||
|
||||
@@ -33,9 +33,7 @@ BackendEndpoints searchBackendEndpoints(Ref ref) {
|
||||
webSearchSettingsControllerProvider.select((s) => s.routeThroughTor),
|
||||
);
|
||||
return BackendEndpoints.fromOrigin(
|
||||
routeThroughTor
|
||||
? SearchBackendConfig.torOriginUri
|
||||
: SearchBackendConfig.originUri,
|
||||
routeThroughTor ? searchBackendTorOriginUri : searchBackendOriginUri,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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)
|
||||
|
||||
@@ -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<SettingsSectionDefinition> extensionsSettingsSections = [
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package: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'
|
||||
|
||||
@@ -41,6 +41,7 @@ const _alwaysAllowPackageExtra = 'eu.weblibre.gatekeeper.always_allow_package';
|
||||
StreamTransformer<Intent, ReceivedIntentParameter>
|
||||
_buildSharingIntentTransformer(
|
||||
IntentGatekeeper gatekeeper,
|
||||
GeneralSettingsRepository settingsRepository,
|
||||
) => StreamTransformer<Intent, ReceivedIntentParameter>.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<Stream<T>> _consumeIntents<T>(
|
||||
Raw<Stream<ReceivedIntentParameter>> 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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
|
||||
@@ -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()]));
|
||||
});
|
||||
|
||||
@@ -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<UserDatabase> with $CacheDaoMixin {
|
||||
|
||||
@@ -67,7 +67,7 @@ class SearchTokensDao extends DatabaseAccessor<UserDatabase> {
|
||||
|
||||
/// Release any reservation older than [maxAge] — covers crashes mid-flight.
|
||||
/// Returns the number of rows released.
|
||||
Future<int> releaseStaleReservations(Duration maxAge) async {
|
||||
Future<int> releaseStaleReservations(Duration maxAge) {
|
||||
final cutoff = DateTime.now().subtract(maxAge);
|
||||
return (update(db.searchTokens)..where(
|
||||
(t) =>
|
||||
|
||||
@@ -261,7 +261,7 @@ class UBlockAssetsRegistry {
|
||||
return count;
|
||||
}
|
||||
|
||||
static UBlockAssetsRegistry fromJson(Map<String, dynamic> json) {
|
||||
factory UBlockAssetsRegistry.fromJson(Map<String, dynamic> json) {
|
||||
final entries = <String, UBlockAssetEntry>{};
|
||||
for (final entry in json.entries) {
|
||||
if (entry.value is Map<String, dynamic>) {
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
* 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/>.
|
||||
*/
|
||||
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
|
||||
|
||||
+7
-13
@@ -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<SandboxCaptureError> 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<List<CaptureTabData>>? _captureTabSub;
|
||||
StreamSubscription<RetryRequest>? _retrySub;
|
||||
|
||||
Stream<SandboxCaptureError> get errors => _errors.stream;
|
||||
Stream<SandboxCaptureError> 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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -361,14 +361,14 @@ class CaptureServer {
|
||||
final tabIdJson = jsonEncode(tabId);
|
||||
final captureIdJson = jsonEncode(captureId);
|
||||
final html =
|
||||
'<!doctype html><html><head><meta charset="utf-8">'
|
||||
'<meta name="viewport" content="width=device-width,initial-scale=1">'
|
||||
'<title>Capturing…</title><style>$_loaderStyles</style>'
|
||||
'</head><body>$body'
|
||||
'<script>window.__TAB_ID__=$tabIdJson;'
|
||||
'window.__CAPTURE_ID__=$captureIdJson;</script>'
|
||||
'<script>$_loaderScript</script>'
|
||||
'</body></html>';
|
||||
'''
|
||||
<!doctype html><html><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Capturing…</title><style>$_loaderStyles</style>
|
||||
</head><body>$body
|
||||
<script>window.__TAB_ID__=$tabIdJson;window.__CAPTURE_ID__=$captureIdJson;</script>
|
||||
<script>$_loaderScript</script>
|
||||
</body></html>''';
|
||||
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 '<main id="pending" class="$pendingClass">'
|
||||
'<div class="spinner"></div>'
|
||||
'<h1>Capturing page<span class="dots"></span></h1>'
|
||||
'<p>Saving an offline copy. This usually takes a few seconds.</p>'
|
||||
'</main>'
|
||||
'<main id="error" class="$errorClass">'
|
||||
'<h1>Capture failed</h1>'
|
||||
'<p>The page could not be saved. Check the notification for details.</p>'
|
||||
'<div class="actions">'
|
||||
'<button id="retry" class="btn" type="button">Retry</button>'
|
||||
'</div>'
|
||||
'</main>';
|
||||
return '''
|
||||
<main id="pending" class="$pendingClass">
|
||||
<div class="spinner"></div>
|
||||
<h1>Capturing page<span class="dots"></span></h1>
|
||||
<p>Saving an offline copy. This usually takes a few seconds.</p>
|
||||
</main>
|
||||
<main id="error" class="$errorClass">
|
||||
<h1>Capture failed</h1>
|
||||
<p>The page could not be saved. Check the notification for details.</p>
|
||||
<div class="actions">
|
||||
<button id="retry" class="btn" type="button">Retry</button>
|
||||
</div>
|
||||
</main>''';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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,
|
||||
isFetching,
|
||||
isFetched,
|
||||
fetchError,
|
||||
_capturingSignature,
|
||||
_capturesSignature,
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
class _FetchFooter extends ConsumerWidget {
|
||||
|
||||
+2
-2
@@ -151,10 +151,10 @@ bool _languageMatchesQuery(String resultLanguage, String? queryTag) {
|
||||
final result = resultLanguage
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(RegExp(r'[-_]'))
|
||||
.split(RegExp('[-_]'))
|
||||
.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;
|
||||
|
||||
|
||||
+10
-10
@@ -16,7 +16,6 @@ dependencies:
|
||||
country_codes: ^3.3.0
|
||||
country_flags: ^4.1.2
|
||||
crypto: ^3.0.7
|
||||
xxh3: ^1.2.0
|
||||
cryptography_flutter: ^2.3.4
|
||||
device_info_plus: ^12.4.0
|
||||
drift: ^2.31.0
|
||||
@@ -31,15 +30,15 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_auto_size_text: ^5.0.0
|
||||
flutter_hooks: ^0.21.3+1
|
||||
flutter_secure_storage: ^10.1.0
|
||||
flutter_markdown: ^0.7.7+1
|
||||
flutter_material_design_icons: ^3.1.0+7447
|
||||
flutter_mozilla_components:
|
||||
path: ../../packages/flutter_mozilla_components
|
||||
flutter_reorderable_grid_view: ^5.6.0
|
||||
flutter_slidable: ^4.0.3
|
||||
flutter_secure_storage: ^10.1.0
|
||||
flutter_singbox_proxy:
|
||||
path: ../../packages/flutter_singbox_proxy
|
||||
flutter_slidable: ^4.0.3
|
||||
flutter_svg: ^2.3.0
|
||||
flutter_tor:
|
||||
path: ../../packages/flutter_tor
|
||||
@@ -67,13 +66,9 @@ dependencies:
|
||||
path: ^1.9.1
|
||||
path_provider: ^2.1.5
|
||||
permission_handler: ^12.0.1
|
||||
pretty_qr_code: ^3.6.0
|
||||
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
|
||||
quick_actions: ^1.1.0
|
||||
riverpod: ^3.2.1
|
||||
@@ -82,6 +77,10 @@ dependencies:
|
||||
rxdart: ^0.28.0
|
||||
saf_stream: ^2.0.0
|
||||
saf_util: ^2.1.0
|
||||
search_client:
|
||||
path: ../../../weblibre_account/packages/search_client
|
||||
search_protocol:
|
||||
path: ../../../weblibre_account/packages/search_protocol
|
||||
secure_archive:
|
||||
git:
|
||||
url: https://github.com/FaFre/secure_archive.git
|
||||
@@ -97,8 +96,8 @@ dependencies:
|
||||
speech_to_text_dialog:
|
||||
path: ../../packages/speech_to_text_dialog
|
||||
sqlite3: ^2.9.4
|
||||
supabase: ^2.10.6
|
||||
sqlite3_flutter_libs: "0.5.41"
|
||||
supabase: ^2.10.6
|
||||
synchronized: ^3.4.0+1
|
||||
text_scroll: ^0.2.1
|
||||
timeago: ^3.7.1
|
||||
@@ -106,8 +105,9 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/FaFre/uri-to-file.git
|
||||
url_launcher: ^6.3.2
|
||||
web_socket_channel: ^3.0.3
|
||||
uuid: ^4.5.3
|
||||
web_socket_channel: ^3.0.3
|
||||
xxh3: ^1.2.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.15.0
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart' show Variable;
|
||||
import 'package:drift/native.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/src/pigeons/gecko.g.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
||||
import 'package:weblibre/domain/services/favicon_resolver.dart';
|
||||
@@ -222,23 +221,26 @@ final _generatorResult = IconResult(
|
||||
);
|
||||
|
||||
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>'
|
||||
'''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<rect width="16" height="16" fill="#ff0000"/>
|
||||
</svg>'''
|
||||
.codeUnits,
|
||||
);
|
||||
|
||||
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>'
|
||||
'''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<circle cx="8" cy="8" r="8" fill="#0000ff"/>
|
||||
</svg>'''
|
||||
.codeUnits,
|
||||
);
|
||||
|
||||
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"/>'
|
||||
'<text x="8" y="11" text-anchor="middle" font-size="8" fill="#fff">W</text>'
|
||||
'</svg>'
|
||||
'''
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<rect width="16" height="16" rx="3" fill="#5B8DEF"/>
|
||||
<text x="8" y="11" text-anchor="middle" font-size="8" fill="#fff">W</text>
|
||||
</svg>'''
|
||||
.codeUnits,
|
||||
);
|
||||
|
||||
+5
-3
@@ -83,19 +83,21 @@ void main() {
|
||||
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
await tester.pump();
|
||||
|
||||
expect(repository.refreshCount, 1);
|
||||
expect(repository.refreshCount(), 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeSubscriptionRepository extends SubscriptionRepository {
|
||||
int refreshCount = 0;
|
||||
int _refreshCount = 0;
|
||||
|
||||
int refreshCount() => _refreshCount;
|
||||
|
||||
@override
|
||||
Future<SubscriptionStatus> build() async => SubscriptionStatus.inactive;
|
||||
|
||||
@override
|
||||
Future<void> refresh() async {
|
||||
refreshCount++;
|
||||
_refreshCount++;
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -18,8 +18,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i5;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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:io';
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i5;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ void main() {
|
||||
test(
|
||||
'container assignments ensure sing-box profiles are running before syncing',
|
||||
() async {
|
||||
final profileId = SingboxProxyConnectionId('profile-1');
|
||||
const profileId = SingboxProxyConnectionId('profile-1');
|
||||
final assignedContainer = _container(
|
||||
id: 'container-1',
|
||||
contextId: 'context-a',
|
||||
|
||||
-1
@@ -439,7 +439,6 @@ void main() {
|
||||
UrlCleanerRule(
|
||||
name: 'test',
|
||||
data: UrlCleanerRuleData(
|
||||
// ignore: avoid_redundant_argument_values
|
||||
completeProvider: false,
|
||||
urlPattern: r'^https?://example\.com',
|
||||
rules: ['track'],
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ void main() {
|
||||
color: Colors.blue,
|
||||
orderKey: 'a',
|
||||
metadata: ContainerMetadata.withDefaults(
|
||||
proxyConnectionId: SingboxProxyConnectionId('missing-proxy'),
|
||||
proxyConnectionId: const SingboxProxyConnectionId('missing-proxy'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -438,7 +438,7 @@ class _FakeContainerRepository extends ContainerRepository {
|
||||
|
||||
class _FakeProxyRoutingSettingsRepository
|
||||
extends ProxyRoutingSettingsRepository {
|
||||
ProxyRoutingSettings? _settings;
|
||||
final ProxyRoutingSettings? _settings;
|
||||
|
||||
_FakeProxyRoutingSettingsRepository([this._settings]);
|
||||
|
||||
|
||||
@@ -127,16 +127,18 @@ void main() {
|
||||
test('buildGroupedParentTree creates correct hierarchy', () {
|
||||
final registry = _makeRegistry();
|
||||
final tree = registry.buildGroupedParentTree();
|
||||
final defaultGroup = tree[UBlockAssetGroup.$default];
|
||||
final adsGroup = tree[UBlockAssetGroup.ads];
|
||||
|
||||
expect(tree, contains(UBlockAssetGroup.$default));
|
||||
expect(tree[UBlockAssetGroup.$default]!, contains('uBlock filters'));
|
||||
expect(defaultGroup, contains('uBlock filters'));
|
||||
expect(
|
||||
tree[UBlockAssetGroup.$default]!['uBlock filters'],
|
||||
defaultGroup?['uBlock filters'],
|
||||
containsAll(['ublock-filters', 'ublock-privacy']),
|
||||
);
|
||||
expect(tree, contains(UBlockAssetGroup.ads));
|
||||
expect(tree[UBlockAssetGroup.ads]!, contains(null));
|
||||
expect(tree[UBlockAssetGroup.ads]![null], contains('easylist'));
|
||||
expect(adsGroup, contains(null));
|
||||
expect(adsGroup?[null], contains('easylist'));
|
||||
expect(tree, contains(UBlockAssetGroup.regions));
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ void main() {
|
||||
);
|
||||
|
||||
test('forces animations off when the app setting is enabled', () {
|
||||
// ignore: avoid_redundant_argument_values
|
||||
const mediaQuery = MediaQueryData(disableAnimations: false);
|
||||
|
||||
final result = applyAppMediaQueryOverrides(
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:drift/native.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/src/pigeons/gecko.g.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
||||
import 'package:weblibre/domain/services/favicon_resolver.dart';
|
||||
@@ -128,7 +128,7 @@ void main() {
|
||||
|
||||
final class _NeverCalledResolver implements FaviconResolver {
|
||||
@override
|
||||
Future<FaviconResolveResult> resolve(Uri url, {int? proxyPort}) async {
|
||||
Future<FaviconResolveResult> resolve(Uri url, {int? proxyPort}) {
|
||||
throw UnimplementedError('cacheOnly should not hit the resolver');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
|
||||
@@ -14,6 +14,7 @@ dependencies:
|
||||
|
||||
flutter_mozilla_components:
|
||||
path: ../
|
||||
hooks_riverpod: ^3.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -69,8 +69,8 @@ export 'src/pigeons/gecko.g.dart'
|
||||
GeckoDeleteBrowsingDataController,
|
||||
GeckoEngineSettings,
|
||||
GeckoFetchResponse,
|
||||
GeckoProxySettings,
|
||||
GeckoPref,
|
||||
GeckoProxySettings,
|
||||
GeckoPublicSuffixListApi,
|
||||
GeckoPwaApi,
|
||||
GeckoSitePermissionsApi,
|
||||
|
||||
+1
-2
@@ -6,11 +6,10 @@
|
||||
// For more information about Flutter integration tests, please see
|
||||
// https://flutter.dev/to/integration-testing
|
||||
|
||||
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
@@ -15,6 +15,7 @@ environment:
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
@@ -25,24 +26,20 @@ dependencies:
|
||||
# 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.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
hooks_riverpod: ^3.3.1
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# 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
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# 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.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:flutter_singbox_proxy_example/main.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('shows proxy runtime status label', (WidgetTester tester) async {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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'
|
||||
show
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('profile model keeps generic sing-box config boundary', () {
|
||||
|
||||
@@ -6,10 +6,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_tor/flutter_tor.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:socks5_proxy/socks_client.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
@@ -19,6 +19,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_tor:
|
||||
path: ../
|
||||
hooks_riverpod: ^3.3.1
|
||||
|
||||
# HTTP client for testing Tor connectivity
|
||||
http: ^1.6.0
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:simple_intent_receiver/simple_intent_receiver.dart';
|
||||
|
||||
@@ -34,7 +35,7 @@ void main() {
|
||||
logger.d(data.action);
|
||||
});
|
||||
|
||||
runApp(const MyApp());
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
@@ -17,6 +17,7 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
hooks_riverpod: ^3.3.1
|
||||
logger: ^2.7.0
|
||||
simple_intent_receiver:
|
||||
# When depending on this package from a real application you should use:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:speech_to_text_dialog/speech_to_text_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
|
||||
@@ -17,6 +17,7 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
hooks_riverpod: ^3.3.1
|
||||
|
||||
speech_to_text_dialog:
|
||||
# When depending on this package from a real application you should use:
|
||||
|
||||
Reference in New Issue
Block a user