gesture feature initial

This commit is contained in:
Fabian Freund
2026-05-31 11:40:57 +02:00
parent 4aecb8966c
commit 4d4c8a8786
45 changed files with 4729 additions and 5 deletions
@@ -223,6 +223,18 @@ GeckoViewportService viewportService(Ref ref) {
return service;
}
@Riverpod(keepAlive: true)
GeckoGestureService gestureService(Ref ref) {
final service = GeckoGestureService();
service.setUp();
ref.onDispose(() async {
await service.dispose();
});
return service;
}
@Riverpod(keepAlive: true)
class EngineReadyState extends _$EngineReadyState {
Future<bool> waitUntilReady({
@@ -293,6 +293,53 @@ final class ViewportServiceProvider
String _$viewportServiceHash() => r'bab39db3180bb6a1cf8c055966b7f5910b41b424';
@ProviderFor(gestureService)
final gestureServiceProvider = GestureServiceProvider._();
final class GestureServiceProvider
extends
$FunctionalProvider<
GeckoGestureService,
GeckoGestureService,
GeckoGestureService
>
with $Provider<GeckoGestureService> {
GestureServiceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'gestureServiceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$gestureServiceHash();
@$internal
@override
$ProviderElement<GeckoGestureService> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
GeckoGestureService create(Ref ref) {
return gestureService(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(GeckoGestureService value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<GeckoGestureService>(value),
);
}
}
String _$gestureServiceHash() => r'81e90a1adb64f596190b52aba0e2ab9dcab3c5e7';
@ProviderFor(EngineReadyState)
final engineReadyStateProvider = EngineReadyStateProvider._();
@@ -43,5 +43,6 @@ enum ToolbarButtonId {
pageDown,
font,
extensionShortcut,
toggleGestures,
quit,
}
@@ -169,6 +169,11 @@ const quitToolbarButtonSpec = ToolbarButtonSpec(
canBeFallbackTarget: false,
);
const toggleGesturesToolbarButtonSpec = ToolbarButtonSpec(
id: ToolbarButtonId.toggleGestures,
defaultVisible: false,
);
const toolbarButtonSpecs = [
backToolbarButtonSpec,
forwardToolbarButtonSpec,
@@ -194,6 +199,7 @@ const toolbarButtonSpecs = [
pageDownToolbarButtonSpec,
fontToolbarButtonSpec,
extensionShortcutToolbarButtonSpec,
toggleGesturesToolbarButtonSpec,
quitToolbarButtonSpec,
];
@@ -47,6 +47,8 @@ import 'package:weblibre/features/geckoview/features/find_in_page/presentation/c
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:weblibre/features/gestures/data/models/gesture_settings.dart';
import 'package:weblibre/features/gestures/domain/repositories/gesture_settings.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart';
import 'package:weblibre/features/user/domain/presentation/dialogs/quit_browser_dialog.dart';
@@ -399,6 +401,34 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
);
},
),
ToolbarButtonDefinition(
spec: toggleGesturesToolbarButtonSpec,
label: 'Gestures',
icon: MdiIcons.gestureSwipe,
builder: (scope, context, ref) {
final on = ref.watch(
gestureSettingsWithDefaultsProvider.select((s) => s.effectiveEnabled),
);
return IconButton(
isSelected: on,
tooltip: on ? 'Disable gestures' : 'Enable gestures',
onPressed: scope.isPreview
? () {}
: () async {
final newActive = !on;
await ref
.read(gestureSettingsRepositoryProvider.notifier)
.updateSettings(
(s) => s.copyWith(
enabled: s.enabled || newActive,
active: newActive,
),
);
},
icon: Icon(on ? MdiIcons.gestureSwipe : MdiIcons.gestureDoubleTap),
);
},
),
ToolbarButtonDefinition(
spec: pageUpToolbarButtonSpec,
label: 'Page Up',
@@ -69,6 +69,8 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/top_sites/domain/repositories/top_site_repository.dart';
import 'package:weblibre/features/gestures/data/models/gesture_settings.dart';
import 'package:weblibre/features/gestures/domain/repositories/gesture_settings.dart';
import 'package:weblibre/features/proxy/data/models/singbox_proxy_profile.dart';
import 'package:weblibre/features/proxy/domain/providers/assigned_proxy_profiles.dart';
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart';
@@ -173,6 +175,16 @@ class _BrowserMenuSheet extends HookConsumerWidget {
_ProfileCard(),
const SizedBox(height: 16),
// Gestures quick toggle (only when the master switch is on)
if (ref.watch(
gestureSettingsWithDefaultsProvider.select(
(s) => s.enabled,
),
)) ...[
const _GestureToggleTile(),
const SizedBox(height: 16),
],
// App
const _SettingsCard(),
const SizedBox(height: 24),
@@ -930,6 +942,36 @@ class _FetchFeedsTile extends HookConsumerWidget {
}
}
// ─── Gestures Quick Toggle ───
class _GestureToggleTile extends ConsumerWidget {
const _GestureToggleTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final active = ref.watch(
gestureSettingsWithDefaultsProvider.select((s) => s.active),
);
return _buildMenuCard(
context,
children: [
SwitchListTile.adaptive(
secondary: const Icon(MdiIcons.gestureSwipe),
title: const Text('Gestures'),
subtitle: Text(active ? 'Active' : 'Suspended'),
value: active,
onChanged: (value) async {
await ref
.read(gestureSettingsRepositoryProvider.notifier)
.updateSettings((s) => s.copyWith(active: value));
},
),
],
);
}
}
// ─── Tab Actions Card ───
class _TabActionsCard extends HookConsumerWidget {
@@ -55,6 +55,8 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selec
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/services/local_index_pruner.dart';
import 'package:weblibre/features/gestures/domain/services/gesture_control.dart';
import 'package:weblibre/features/gestures/presentation/widgets/gesture_feedback_overlay.dart';
import 'package:weblibre/features/intent_gatekeeper/domain/entities/intent_source_policy.dart';
import 'package:weblibre/features/intent_gatekeeper/domain/entities/pending_intent_decision.dart';
import 'package:weblibre/features/intent_gatekeeper/domain/services/intent_gatekeeper.dart';
@@ -304,6 +306,7 @@ class _BrowserViewState extends ConsumerState<BrowserView>
),
),
if (showHome) const Positioned.fill(child: BrowserHome()),
const Positioned.fill(child: GestureFeedbackOverlay()),
],
),
);
@@ -610,6 +613,19 @@ class _BrowserViewState extends ConsumerState<BrowserView>
},
);
ref.listenManual(
fireImmediately: true,
gestureControlServiceProvider,
(previous, next) {},
onError: (error, stackTrace) {
logger.e(
'Error listening to gestureControlServiceProvider',
error: error,
stackTrace: stackTrace,
);
},
);
ref.listenManual(
fireImmediately: true,
proxySettingsReplicationProvider,
@@ -42,7 +42,7 @@ final class GeckoInferenceRepositoryProvider
}
String _$geckoInferenceRepositoryHash() =>
r'c4f52d2edfb8c5763577a17697202016645177da';
r'cd6d47ccb5aa8d64aba81ac1d982b1df8cf64d14';
abstract class _$GeckoInferenceRepository extends $Notifier<void> {
void build();
@@ -389,7 +389,7 @@ final class ContainerTabSuggestionsProvider
}
String _$containerTabSuggestionsHash() =>
r'958c7432aa5c2a2828fcccd0127e0110d0a008cf';
r'a29a43773000b415f3e8a23db281078c30c3b10d';
final class ContainerTabSuggestionsFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<String>?>, String?> {