fix content overlay issues; top bar now also hiding on scroll;
This commit is contained in:
@@ -126,14 +126,11 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
final tabId = ref.watch(selectedTabProvider);
|
final tabId = ref.watch(selectedTabProvider);
|
||||||
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||||
|
|
||||||
final autoHideTabBar = switch (tabBarPosition) {
|
final autoHideTabBar = ref.watch(
|
||||||
TabBarPosition.top => false,
|
generalSettingsWithDefaultsProvider.select(
|
||||||
TabBarPosition.bottom => ref.watch(
|
(value) => value.autoHideTabBar,
|
||||||
generalSettingsWithDefaultsProvider.select(
|
|
||||||
(value) => value.autoHideTabBar,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
};
|
);
|
||||||
|
|
||||||
// Auto-hide scroll detection hooks (run unconditionally per hook rules)
|
// Auto-hide scroll detection hooks (run unconditionally per hook rules)
|
||||||
final diffAcc = useRef(0.0);
|
final diffAcc = useRef(0.0);
|
||||||
@@ -154,7 +151,7 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
}, [tabId, autoHideTabBar]);
|
}, [tabId, autoHideTabBar]);
|
||||||
|
|
||||||
useOnAppLifecycleStateChange((previous, current) {
|
useOnAppLifecycleStateChange((previous, current) {
|
||||||
if (!autoHideTabBar) return;
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
||||||
if (current == AppLifecycleState.resumed) {
|
if (current == AppLifecycleState.resumed) {
|
||||||
resetHiddenState();
|
resetHiddenState();
|
||||||
}
|
}
|
||||||
@@ -164,7 +161,7 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
previous,
|
previous,
|
||||||
next,
|
next,
|
||||||
) {
|
) {
|
||||||
if (!autoHideTabBar) return;
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
||||||
if (next == true) {
|
if (next == true) {
|
||||||
resetHiddenState();
|
resetHiddenState();
|
||||||
}
|
}
|
||||||
@@ -174,7 +171,7 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
previous,
|
previous,
|
||||||
next,
|
next,
|
||||||
) {
|
) {
|
||||||
if (!autoHideTabBar) return;
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
||||||
if (next != null && previous != null) {
|
if (next != null && previous != null) {
|
||||||
if (previous != next) {
|
if (previous != next) {
|
||||||
resetHiddenState();
|
resetHiddenState();
|
||||||
@@ -185,7 +182,7 @@ class _TabBar extends HookConsumerWidget {
|
|||||||
useOnStreamChange(
|
useOnStreamChange(
|
||||||
pointerMoveEvents,
|
pointerMoveEvents,
|
||||||
onData: (event) {
|
onData: (event) {
|
||||||
if (!autoHideTabBar) return;
|
if (!ref.read(generalSettingsWithDefaultsProvider).autoHideTabBar) return;
|
||||||
final diff = event.dy;
|
final diff = event.dy;
|
||||||
if (diff < 0) {
|
if (diff < 0) {
|
||||||
if (diffAcc.value > 0) {
|
if (diffAcc.value > 0) {
|
||||||
@@ -255,6 +252,12 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final autoHideTabBar = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.autoHideTabBar,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final displayAppBar = useValueNotifier(true);
|
final displayAppBar = useValueNotifier(true);
|
||||||
|
|
||||||
ref.listen(tabBarDismissableControllerProvider, (previous, next) {
|
ref.listen(tabBarDismissableControllerProvider, (previous, next) {
|
||||||
@@ -286,11 +289,11 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Compute visibility states for toolbars
|
// Compute visibility states for toolbars
|
||||||
final appBarVisible = useValueListenable(displayAppBar);
|
final appBarVisible = useValueListenable(displayAppBar);
|
||||||
final topDismissed = ref.watch(tabBarDismissableControllerProvider);
|
|
||||||
|
|
||||||
// Toolbar is visible when: sheet is shown OR (not fullscreen AND app bar visible)
|
// Toolbar is visible when: sheet is shown OR (not fullscreen AND app bar visible)
|
||||||
|
// appBarVisible reflects both scroll-based auto-hide and swipe dismiss state
|
||||||
final topToolbarVisible =
|
final topToolbarVisible =
|
||||||
sheetDisplayed || (!tabInFullScreen && !topDismissed);
|
sheetDisplayed || (!tabInFullScreen && appBarVisible);
|
||||||
final bottomToolbarVisible =
|
final bottomToolbarVisible =
|
||||||
sheetDisplayed || (!tabInFullScreen && appBarVisible);
|
sheetDisplayed || (!tabInFullScreen && appBarVisible);
|
||||||
|
|
||||||
@@ -310,6 +313,29 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
final bottomAppBarTotalHeight =
|
final bottomAppBarTotalHeight =
|
||||||
bottomAppBarContentSize.height + bottomSafeArea;
|
bottomAppBarContentSize.height + bottomSafeArea;
|
||||||
|
|
||||||
|
// When auto-hide is disabled, constrain browser to not extend behind toolbar
|
||||||
|
// (unless toolbar is manually dismissed via swipe gesture)
|
||||||
|
final browserBottomOffset = (!autoHideTabBar && bottomToolbarVisible)
|
||||||
|
? bottomAppBarTotalHeight
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
// Calculate top toolbar size for browser offset
|
||||||
|
final topSafeArea = MediaQuery.of(context).padding.top;
|
||||||
|
final topAppBarContentSize = BrowserTopAppBar(
|
||||||
|
showMainToolbar: tabBarPosition == TabBarPosition.top,
|
||||||
|
showContextualToolbar: showContextualToolbar,
|
||||||
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
|
).preferredSize;
|
||||||
|
final topAppBarTotalHeight = topAppBarContentSize.height + topSafeArea;
|
||||||
|
|
||||||
|
// For top bar: always constrain browser below toolbar when visible
|
||||||
|
// (unlike bottom bar which only constrains when auto-hide is disabled)
|
||||||
|
// This ensures top-of-page content is always accessible
|
||||||
|
final browserTopOffset =
|
||||||
|
(tabBarPosition == TabBarPosition.top && topToolbarVisible)
|
||||||
|
? topAppBarTotalHeight
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
// Theme with dynamic snackbar margin to position above bottom toolbar
|
// Theme with dynamic snackbar margin to position above bottom toolbar
|
||||||
final themeData = Theme.of(context).copyWith(
|
final themeData = Theme.of(context).copyWith(
|
||||||
bottomSheetTheme: BottomSheetThemeData(
|
bottomSheetTheme: BottomSheetThemeData(
|
||||||
@@ -343,14 +369,21 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Layer 0: Browser content (fills entire Stack - constant dimensions)
|
// Layer 0: Browser content
|
||||||
Positioned.fill(
|
// Position changes instantly (no animation) to avoid jarring native view resize
|
||||||
|
// The toolbar itself animates, providing visual continuity
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: browserTopOffset,
|
||||||
|
bottom: browserBottomOffset,
|
||||||
child: _Browser(
|
child: _Browser(
|
||||||
overlayController: overlayController,
|
overlayController: overlayController,
|
||||||
displayAppBar: displayAppBar,
|
displayAppBar: displayAppBar,
|
||||||
tabInFullScreen: tabInFullScreen,
|
tabInFullScreen: tabInFullScreen,
|
||||||
pointerMoveEventSink: pointerMoveEventsController.sink,
|
pointerMoveEventSink: pointerMoveEventsController.sink,
|
||||||
sheetDisplayed: sheetDisplayed,
|
sheetDisplayed: sheetDisplayed,
|
||||||
|
hasTopBarOffset: browserTopOffset > 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -382,7 +415,10 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
||||||
showContextualToolbar: showContextualToolbar,
|
showContextualToolbar: showContextualToolbar,
|
||||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
pointerMoveEvents: pointerMoveEventsController.stream,
|
// Only subscribe to scroll events when this is the active position
|
||||||
|
pointerMoveEvents: tabBarPosition == TabBarPosition.bottom
|
||||||
|
? pointerMoveEventsController.stream
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -402,7 +438,7 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
displayAppBar: displayAppBar,
|
displayAppBar: displayAppBar,
|
||||||
showContextualToolbar: showContextualToolbar,
|
showContextualToolbar: showContextualToolbar,
|
||||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||||
pointerMoveEvents: null,
|
pointerMoveEvents: pointerMoveEventsController.stream,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -493,6 +529,7 @@ class _Browser extends HookConsumerWidget {
|
|||||||
final StreamSink<Offset> pointerMoveEventSink;
|
final StreamSink<Offset> pointerMoveEventSink;
|
||||||
final bool tabInFullScreen;
|
final bool tabInFullScreen;
|
||||||
final bool sheetDisplayed;
|
final bool sheetDisplayed;
|
||||||
|
final bool hasTopBarOffset;
|
||||||
|
|
||||||
const _Browser({
|
const _Browser({
|
||||||
required this.overlayController,
|
required this.overlayController,
|
||||||
@@ -500,6 +537,7 @@ class _Browser extends HookConsumerWidget {
|
|||||||
required this.tabInFullScreen,
|
required this.tabInFullScreen,
|
||||||
required this.pointerMoveEventSink,
|
required this.pointerMoveEventSink,
|
||||||
required this.sheetDisplayed,
|
required this.sheetDisplayed,
|
||||||
|
required this.hasTopBarOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -654,6 +692,7 @@ class _Browser extends HookConsumerWidget {
|
|||||||
child: _BrowserView(
|
child: _BrowserView(
|
||||||
isFullscreen: tabInFullScreen,
|
isFullscreen: tabInFullScreen,
|
||||||
pointerMoveEventSink: pointerMoveEventSink,
|
pointerMoveEventSink: pointerMoveEventSink,
|
||||||
|
hasTopBarOffset: hasTopBarOffset,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -666,13 +705,19 @@ class _Browser extends HookConsumerWidget {
|
|||||||
class _BrowserView extends StatelessWidget {
|
class _BrowserView extends StatelessWidget {
|
||||||
final bool isFullscreen;
|
final bool isFullscreen;
|
||||||
final StreamSink<Offset>? pointerMoveEventSink;
|
final StreamSink<Offset>? pointerMoveEventSink;
|
||||||
|
final bool hasTopBarOffset;
|
||||||
|
|
||||||
const _BrowserView({required this.isFullscreen, this.pointerMoveEventSink});
|
const _BrowserView({
|
||||||
|
required this.isFullscreen,
|
||||||
|
required this.hasTopBarOffset,
|
||||||
|
this.pointerMoveEventSink,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
top: !isFullscreen,
|
// Disable top SafeArea when top bar handles it (has offset applied)
|
||||||
|
top: !isFullscreen && !hasTopBarOffset,
|
||||||
right: !isFullscreen,
|
right: !isFullscreen,
|
||||||
// Bottom SafeArea is handled by the overlay toolbar (BottomAppBar)
|
// Bottom SafeArea is handled by the overlay toolbar (BottomAppBar)
|
||||||
bottom: false,
|
bottom: false,
|
||||||
|
|||||||
+18
-3
@@ -210,6 +210,12 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final tabBarPosition = ref.watch(
|
||||||
|
generalSettingsWithDefaultsProvider.select(
|
||||||
|
(value) => value.tabBarPosition,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final dragStartPosition = useRef(Offset.zero);
|
final dragStartPosition = useRef(Offset.zero);
|
||||||
|
|
||||||
final toolbarHeight = useMemoized(() => getToolbarHeight());
|
final toolbarHeight = useMemoized(() => getToolbarHeight());
|
||||||
@@ -259,9 +265,18 @@ class BrowserTabBar extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (distance.dy < (toolbarHeight / 3) &&
|
} else if (distance.dx.abs() < 15) {
|
||||||
distance.dx.abs() < 15) {
|
// Swipe direction for dismiss depends on toolbar position:
|
||||||
ref.read(tabBarDismissableControllerProvider.notifier).dismiss();
|
// - Bottom bar: swipe down to dismiss (distance.dy negative or small positive)
|
||||||
|
// - Top bar: swipe up to dismiss (distance.dy positive or small negative)
|
||||||
|
final dismissThreshold = toolbarHeight / 3;
|
||||||
|
final shouldDismiss = switch (tabBarPosition) {
|
||||||
|
TabBarPosition.bottom => distance.dy < dismissThreshold,
|
||||||
|
TabBarPosition.top => distance.dy > -dismissThreshold,
|
||||||
|
};
|
||||||
|
if (shouldDismiss) {
|
||||||
|
ref.read(tabBarDismissableControllerProvider.notifier).dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
Reference in New Issue
Block a user