diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index bb15a884..7104b67f 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -57,8 +57,6 @@ class BrowserScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final scaffoldKey = useMemoized(() => GlobalKey()); - final eventService = ref.watch(eventServiceProvider); final sheetDisplayed = ref.watch( @@ -106,56 +104,6 @@ class BrowserScreen extends HookConsumerWidget { ); final sheetController = useRef(null); - ref.listen(bottomSheetControllerProvider, (previous, next) { - final state = scaffoldKey.currentState; - if (state != null) { - if (sheetController.value != null) { - sheetController.value!.close(); - sheetController.value = null; - } - - if (next != null) { - final relativeSafeArea = MediaQuery.of(context).relativeSafeArea(); - - sheetController.value = state.showBottomSheet((context) { - bool dismissOnThreshold( - DraggableScrollableNotification notification, - ) { - if (notification.extent <= 0.1) { - ref.read(bottomSheetControllerProvider.notifier).dismiss(); - return true; - } else { - ref - .read(bottomSheetExtendProvider.notifier) - .add(notification.extent); - } - - return false; - } - - final sheet = switch (next) { - ViewTabsSheet() => - NotificationListener( - key: ValueKey(next), - onNotification: dismissOnThreshold, - child: _ViewTabsSheet(maxChildSize: relativeSafeArea), - ), - final EditUrlSheet parameter => - NotificationListener( - key: ValueKey(parameter), - onNotification: dismissOnThreshold, - child: _ViewUrlSheet( - initialTabState: parameter.tabState, - maxChildSize: relativeSafeArea, - ), - ), - }; - - return sheet; - }); - } - } - }); return PopScope( //We need this for BackButtonListener to work downstream @@ -164,7 +112,6 @@ class BrowserScreen extends HookConsumerWidget { child: Theme( data: themeData, child: Scaffold( - key: scaffoldKey, extendBodyBehindAppBar: tabInFullScreen, bottomNavigationBar: HookConsumer( builder: (context, ref, child) { @@ -265,161 +212,221 @@ class BrowserScreen extends HookConsumerWidget { ); }, ), - body: DragTarget( - onMove: (details) { - ref - .read(willAcceptDropProvider.notifier) - .setData(DeleteDropData(details.data.tabId)); - }, - onLeave: (data) { - ref.read(willAcceptDropProvider.notifier).clear(); - }, - onAcceptWithDetails: (details) async { - ref.read(willAcceptDropProvider.notifier).clear(); - await ref - .read(tabRepositoryProvider.notifier) - .closeTab(details.data.tabId); + body: Consumer( + child: DragTarget( + onMove: (details) { + ref + .read(willAcceptDropProvider.notifier) + .setData(DeleteDropData(details.data.tabId)); + }, + onLeave: (data) { + ref.read(willAcceptDropProvider.notifier).clear(); + }, + onAcceptWithDetails: (details) async { + ref.read(willAcceptDropProvider.notifier).clear(); + await ref + .read(tabRepositoryProvider.notifier) + .closeTab(details.data.tabId); - if (context.mounted) { - ui_helper.showTabUndoClose( - context, - ref.read(tabRepositoryProvider.notifier).undoClose, - ); - } - }, - builder: (context, _, _) { - return OverlayPortal( - controller: overlayController, - overlayChildBuilder: (context) { - return overlayBuilder!.call(context); - }, - child: Listener( - onPointerDown: sheetDisplayed - ? (_) { + if (context.mounted) { + ui_helper.showTabUndoClose( + context, + ref.read(tabRepositoryProvider.notifier).undoClose, + ); + } + }, + builder: (context, _, _) { + return OverlayPortal( + controller: overlayController, + overlayChildBuilder: (context) { + return overlayBuilder!.call(context); + }, + child: Listener( + onPointerDown: sheetDisplayed + ? (_) { + ref + .read(bottomSheetControllerProvider.notifier) + .dismiss(); + } + : null, + child: BackButtonListener( + onBackButtonPressed: () async { + final tabState = ref.read(selectedTabStateProvider); + + final tabCount = ref.read( + tabListProvider.select((tabs) => tabs.value.length), + ); + + //Don't do anything if a child route is active + if (GoRouterState.of(context).topRoute?.name != + BrowserRoute.name) { + return false; + } + + if (sheetDisplayed) { ref .read(bottomSheetControllerProvider.notifier) .dismiss(); + return true; } - : null, - child: BackButtonListener( - onBackButtonPressed: () async { - final tabState = ref.read(selectedTabStateProvider); - final tabCount = ref.read( - tabListProvider.select((tabs) => tabs.value.length), - ); + if (overlayBuilder != null) { + ref + .read(overlayControllerProvider.notifier) + .dismiss(); + return true; + } - //Don't do anything if a child route is active - if (GoRouterState.of(context).topRoute?.name != - BrowserRoute.name) { - return false; - } - - if (sheetDisplayed) { - ref - .read(bottomSheetControllerProvider.notifier) - .dismiss(); - return true; - } - - if (overlayBuilder != null) { - ref.read(overlayControllerProvider.notifier).dismiss(); - return true; - } - - if (tabState?.isFullScreen == true) { - await ref - .read(selectedTabSessionNotifierProvider) - .exitFullscreen(); - return true; - } - - if (tabState?.isLoading == true) { - lastBackButtonPress.value = null; - - final controller = ref.read( - selectedTabSessionNotifierProvider, - ); - - await controller.stopLoading(); - return true; - } else if (tabState?.readerableState.active == true) { - lastBackButtonPress.value = null; - - await ref - .read(readerableScreenControllerProvider.notifier) - .toggleReaderView(false); - - return true; - } else if (tabState?.historyState.canGoBack == true) { - lastBackButtonPress.value = null; - - final controller = ref.read( - selectedTabSessionNotifierProvider, - ); - - await controller.goBack(); - return true; - } - - //Go router has routes to go back to - if (context.canPop()) { - return true; - } - - if (ref - .read(tabRepositoryProvider.notifier) - .hasLaunchedFromIntent(tabState?.id)) { - //Mark back as unhandled and navigator will pop - await SystemNavigator.pop(); - return false; - } - - if (lastBackButtonPress.value != null && - DateTime.now().difference( - lastBackButtonPress.value!, - ) < - _backButtonPressTimeout) { - lastBackButtonPress.value = null; - - if (tabState != null && tabCount > 1) { + if (tabState?.isFullScreen == true) { await ref - .read(tabRepositoryProvider.notifier) - .closeTab(tabState.id); + .read(selectedTabSessionNotifierProvider) + .exitFullscreen(); + return true; + } - if (context.mounted) { - ui_helper.showTabUndoClose( - context, - ref - .read(tabRepositoryProvider.notifier) - .undoClose, - ); - } + if (tabState?.isLoading == true) { + lastBackButtonPress.value = null; + + final controller = ref.read( + selectedTabSessionNotifierProvider, + ); + + await controller.stopLoading(); + return true; + } else if (tabState?.readerableState.active == true) { + lastBackButtonPress.value = null; + + await ref + .read(readerableScreenControllerProvider.notifier) + .toggleReaderView(false); return true; - } else { + } else if (tabState?.historyState.canGoBack == true) { + lastBackButtonPress.value = null; + + final controller = ref.read( + selectedTabSessionNotifierProvider, + ); + + await controller.goBack(); + return true; + } + + //Go router has routes to go back to + if (context.canPop()) { + return true; + } + + if (ref + .read(tabRepositoryProvider.notifier) + .hasLaunchedFromIntent(tabState?.id)) { //Mark back as unhandled and navigator will pop await SystemNavigator.pop(); return false; } - } else { - lastBackButtonPress.value = DateTime.now(); - ui_helper.showTabBackButtonMessage( - context, - tabCount, - _backButtonPressTimeout, - ); - return true; - } - }, - child: _BrowserView( - sheetDisplayed: sheetDisplayed, - isFullscreen: tabInFullScreen, + if (lastBackButtonPress.value != null && + DateTime.now().difference( + lastBackButtonPress.value!, + ) < + _backButtonPressTimeout) { + lastBackButtonPress.value = null; + + if (tabState != null && tabCount > 1) { + await ref + .read(tabRepositoryProvider.notifier) + .closeTab(tabState.id); + + if (context.mounted) { + ui_helper.showTabUndoClose( + context, + ref + .read(tabRepositoryProvider.notifier) + .undoClose, + ); + } + + return true; + } else { + //Mark back as unhandled and navigator will pop + await SystemNavigator.pop(); + return false; + } + } else { + lastBackButtonPress.value = DateTime.now(); + ui_helper.showTabBackButtonMessage( + context, + tabCount, + _backButtonPressTimeout, + ); + + return true; + } + }, + child: _BrowserView( + sheetDisplayed: sheetDisplayed, + isFullscreen: tabInFullScreen, + ), ), ), - ), - ); + ); + }, + ), + builder: (context, ref, child) { + ref.listen(bottomSheetControllerProvider, (previous, next) { + if (sheetController.value != null) { + sheetController.value!.close(); + sheetController.value = null; + } + + if (next != null) { + final relativeSafeArea = MediaQuery.of( + context, + ).relativeSafeArea(); + sheetController.value = Scaffold.of(context).showBottomSheet(( + context, + ) { + bool dismissOnThreshold( + DraggableScrollableNotification notification, + ) { + if (notification.extent <= 0.1) { + ref + .read(bottomSheetControllerProvider.notifier) + .dismiss(); + return true; + } else { + ref + .read(bottomSheetExtendProvider.notifier) + .add(notification.extent); + } + + return false; + } + + final sheet = switch (next) { + ViewTabsSheet() => + NotificationListener( + key: ValueKey(next), + onNotification: dismissOnThreshold, + child: _ViewTabsSheet(maxChildSize: relativeSafeArea), + ), + final EditUrlSheet parameter => + NotificationListener( + key: ValueKey(parameter), + onNotification: dismissOnThreshold, + child: _ViewUrlSheet( + initialTabState: parameter.tabState, + maxChildSize: relativeSafeArea, + ), + ), + }; + + return sheet; + }); + } + }); + + return child!; }, ), floatingActionButton: ReaderAppearanceButton(), diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart index 9e36e50e..cca77702 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart @@ -37,65 +37,8 @@ class DraggableScrollableHeader extends StatefulWidget { super.key, required this.controller, required this.child, - this.velocitySensitivity = 0.35, // Higher = more sensitive to velocity - this.maxVelocity = 2500.0, // Maximum velocity to consider - this.minVelocity = 150.0, // Minimum velocity to trigger momentum - this.animationDuration = const Duration(milliseconds: 300), - this.animationCurve = Curves.decelerate, - this.dragSensitivity = 1.0, // Controls drag responsiveness }); - @override - State createState() => - _DraggableScrollableHeaderState(); -} - -class _DraggableScrollableHeaderState extends State - with TickerProviderStateMixin { - late AnimationController _animationController; - late Animation? _animation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - vsync: this, - duration: widget.animationDuration, - ); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - - Future _animateToPosition( - double targetSize, { - Duration? customDuration, - }) async { - if (customDuration != null) { - _animationController.duration = customDuration; - } else { - _animationController.duration = widget.animationDuration; - } - - _animation = Tween(begin: widget.controller.size, end: targetSize) - .animate( - CurvedAnimation( - parent: _animationController, - curve: widget.animationCurve, - ), - ); - - _animationController.reset(); - await _animationController.forward(); - - _animation!.addListener(() { - widget.controller.jumpTo(_animation!.value); - }); - } - @override Widget build(BuildContext context) { return GestureDetector( diff --git a/app/pubspec.yaml b/app/pubspec.yaml index fbad0901..bafe89d6 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -2,7 +2,7 @@ name: weblibre description: "The Privacy-Focused & AI-Powered Research Browser" publish_to: 'none' resolution: workspace -version: 0.9.23-alpha-1+26 +version: 0.9.23-alpha-2+26 environment: sdk: '>=3.8.0 <4.0.0'