app bar positioning
This commit is contained in:
@@ -300,13 +300,13 @@ AsyncValue<String?> selectedTabContainerId(Ref ref) {
|
||||
return const AsyncData(null);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<int> tabScrollY(Ref ref, String? tabId, Duration sampleTime) {
|
||||
final eventService = ref.watch(eventServiceProvider);
|
||||
// @Riverpod()
|
||||
// Stream<int> tabScrollY(Ref ref, String? tabId, Duration sampleTime) {
|
||||
// final eventService = ref.watch(eventServiceProvider);
|
||||
|
||||
return eventService.scrollEvent
|
||||
.where((event) => event.tabId == tabId)
|
||||
.sampleTime(sampleTime)
|
||||
.map((event) => event.scrollY)
|
||||
.asBroadcastStream();
|
||||
}
|
||||
// return eventService.scrollEvent
|
||||
// .where((event) => event.tabId == tabId)
|
||||
// .sampleTime(sampleTime)
|
||||
// .map((event) => event.scrollY)
|
||||
// .asBroadcastStream();
|
||||
// }
|
||||
|
||||
@@ -337,72 +337,3 @@ final class SelectedTabContainerIdProvider
|
||||
|
||||
String _$selectedTabContainerIdHash() =>
|
||||
r'07899d29f69654d3b314d0da945ad402b1003b41';
|
||||
|
||||
@ProviderFor(tabScrollY)
|
||||
const tabScrollYProvider = TabScrollYFamily._();
|
||||
|
||||
final class TabScrollYProvider
|
||||
extends $FunctionalProvider<AsyncValue<int>, int, Stream<int>>
|
||||
with $FutureModifier<int>, $StreamProvider<int> {
|
||||
const TabScrollYProvider._({
|
||||
required TabScrollYFamily super.from,
|
||||
required (String?, Duration) super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'tabScrollYProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$tabScrollYHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'tabScrollYProvider'
|
||||
''
|
||||
'$argument';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<int> $createElement($ProviderPointer pointer) =>
|
||||
$StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<int> create(Ref ref) {
|
||||
final argument = this.argument as (String?, Duration);
|
||||
return tabScrollY(ref, argument.$1, argument.$2);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabScrollYProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabScrollYHash() => r'dcb1e82b42bab98c4fc2ee4c7a22f37a28b8ce9e';
|
||||
|
||||
final class TabScrollYFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<int>, (String?, Duration)> {
|
||||
const TabScrollYFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'tabScrollYProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
TabScrollYProvider call(String? tabId, Duration sampleTime) =>
|
||||
TabScrollYProvider._(argument: (tabId, sampleTime), from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'tabScrollYProvider';
|
||||
}
|
||||
|
||||
@@ -50,20 +50,27 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
class _TabBar extends HookConsumerWidget {
|
||||
final bool showMainToolbar;
|
||||
final bool showContextualToolbar;
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final ValueNotifier<bool> showAppBar;
|
||||
final ValueNotifier<bool> displayAppBar;
|
||||
final ValueNotifier<PersistentBottomSheetController?> sheetController;
|
||||
final Stream<Offset>? pointerMoveEvents;
|
||||
final TabBarPosition tabBarPosition;
|
||||
|
||||
const _TabBar({
|
||||
required this.showMainToolbar,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.showAppBar,
|
||||
required this.displayAppBar,
|
||||
required this.sheetController,
|
||||
required this.tabBarPosition,
|
||||
required this.pointerMoveEvents,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -75,29 +82,43 @@ class _TabBar extends HookConsumerWidget {
|
||||
selectedTabStateProvider.select((value) => value?.isFullScreen ?? false),
|
||||
);
|
||||
|
||||
final autoHideTabBar = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.autoHideTabBar,
|
||||
final autoHideTabBar = switch (tabBarPosition) {
|
||||
TabBarPosition.top => false,
|
||||
TabBarPosition.bottom => ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.autoHideTabBar,
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
final appBarVisible = tabBarPosition == TabBarPosition.top
|
||||
? !ref.watch(tabBarDismissableControllerProvider)
|
||||
: useValueListenable(displayAppBar);
|
||||
|
||||
if (!autoHideTabBar) {
|
||||
return Visibility(
|
||||
visible: !tabInFullScreen,
|
||||
child: BrowserBottomAppBar(
|
||||
displayedSheet: displayedSheet,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
visible: !tabInFullScreen && appBarVisible,
|
||||
child: switch (tabBarPosition) {
|
||||
TabBarPosition.top => BrowserTopAppBar(
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
TabBarPosition.bottom => BrowserBottomAppBar(
|
||||
displayedSheet: displayedSheet,
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final appBarVisible = useValueListenable(showAppBar);
|
||||
final diffAcc = useRef(0.0);
|
||||
|
||||
void resetHiddenState() {
|
||||
if (!ref.read(tabBarDismissableControllerProvider)) {
|
||||
showAppBar.value = true;
|
||||
displayAppBar.value = true;
|
||||
}
|
||||
|
||||
diffAcc.value = 0.0;
|
||||
@@ -137,12 +158,10 @@ class _TabBar extends HookConsumerWidget {
|
||||
}
|
||||
});
|
||||
|
||||
ref.listen(tabScrollYProvider(tabId, const Duration(milliseconds: 50)), (
|
||||
previous,
|
||||
next,
|
||||
) {
|
||||
if (previous?.value != null && next.value != null) {
|
||||
final diff = previous!.value! - next.value!;
|
||||
useOnStreamChange(
|
||||
pointerMoveEvents,
|
||||
onData: (event) {
|
||||
final diff = event.dy;
|
||||
if (diff < 0) {
|
||||
if (diffAcc.value > 0) {
|
||||
diffAcc.value = 0.0;
|
||||
@@ -150,7 +169,7 @@ class _TabBar extends HookConsumerWidget {
|
||||
|
||||
diffAcc.value += diff;
|
||||
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
|
||||
showAppBar.value = false;
|
||||
displayAppBar.value = false;
|
||||
}
|
||||
} else if (diff > 0) {
|
||||
if (diffAcc.value < 0) {
|
||||
@@ -162,18 +181,26 @@ class _TabBar extends HookConsumerWidget {
|
||||
resetHiddenState();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return Visibility(
|
||||
visible:
|
||||
sheetController.value != null || (!tabInFullScreen && appBarVisible),
|
||||
maintainState: true,
|
||||
child: BrowserBottomAppBar(
|
||||
displayedSheet: displayedSheet,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
child: switch (tabBarPosition) {
|
||||
TabBarPosition.top => BrowserTopAppBar(
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
TabBarPosition.bottom => BrowserBottomAppBar(
|
||||
showMainToolbar: showMainToolbar,
|
||||
displayedSheet: displayedSheet,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -191,7 +218,13 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
|
||||
final overlayController = useOverlayPortalController();
|
||||
|
||||
final showContextualAppBar = ref.watch(
|
||||
final tabBarPosition = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarPosition,
|
||||
),
|
||||
);
|
||||
|
||||
final showContextualToolbar = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarShowContextualBar,
|
||||
),
|
||||
@@ -203,10 +236,14 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
final showAppBar = useValueNotifier(true);
|
||||
final displayAppBar = useValueNotifier(true);
|
||||
final removeTopAppBar = useState(false);
|
||||
|
||||
ref.listen(tabBarDismissableControllerProvider, (previous, next) {
|
||||
showAppBar.value = !next;
|
||||
displayAppBar.value = !next;
|
||||
if (tabBarPosition == TabBarPosition.bottom) {
|
||||
removeTopAppBar.value = next;
|
||||
}
|
||||
});
|
||||
|
||||
ref.listen(overlayControllerProvider, (previous, next) {
|
||||
@@ -244,6 +281,8 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
|
||||
final sheetController = useState<PersistentBottomSheetController?>(null);
|
||||
|
||||
final pointerMoveEventsController = useStreamController<Offset>();
|
||||
|
||||
return PopScope(
|
||||
//We need this for BackButtonListener to work downstream
|
||||
//No direct pop result will be handled here
|
||||
@@ -256,23 +295,50 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
//This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely
|
||||
return null;
|
||||
},
|
||||
body: Column(
|
||||
children: [
|
||||
_TabBar(
|
||||
showAppBar: showAppBar,
|
||||
sheetController: sheetController,
|
||||
showContextualToolbar: showContextualAppBar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
),
|
||||
Expanded(
|
||||
child: _Browser(
|
||||
overlayController: overlayController,
|
||||
sheetController: sheetController,
|
||||
showAppBar: showAppBar,
|
||||
tabInFullScreen: tabInFullScreen,
|
||||
),
|
||||
),
|
||||
],
|
||||
appBar: (tabBarPosition == TabBarPosition.top)
|
||||
? PreferredSize(
|
||||
preferredSize: BrowserTopAppBar(
|
||||
showMainToolbar: true,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
).preferredSize,
|
||||
child: _TabBar(
|
||||
tabBarPosition: TabBarPosition.top,
|
||||
showMainToolbar: true,
|
||||
displayAppBar: displayAppBar,
|
||||
sheetController: sheetController,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
pointerMoveEvents: null,
|
||||
),
|
||||
)
|
||||
: removeTopAppBar.value
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.zero,
|
||||
child: SizedBox.shrink(),
|
||||
)
|
||||
: null,
|
||||
bottomNavigationBar: _TabBar(
|
||||
tabBarPosition: TabBarPosition.bottom,
|
||||
displayAppBar: displayAppBar,
|
||||
sheetController: sheetController,
|
||||
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
pointerMoveEvents: pointerMoveEventsController.stream,
|
||||
),
|
||||
body: _Browser(
|
||||
overlayController: overlayController,
|
||||
sheetController: sheetController,
|
||||
displayAppBar: displayAppBar,
|
||||
tabInFullScreen: tabInFullScreen,
|
||||
pointerMoveEventSink: pointerMoveEventsController.sink,
|
||||
bottomAppBarSize: BrowserBottomAppBar(
|
||||
showMainToolbar: false,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
displayedSheet: null,
|
||||
).preferredSize,
|
||||
),
|
||||
floatingActionButton: BrowserFab(),
|
||||
),
|
||||
@@ -286,15 +352,19 @@ class _Browser extends HookConsumerWidget {
|
||||
|
||||
final OverlayPortalController overlayController;
|
||||
final ValueNotifier<PersistentBottomSheetController?> sheetController;
|
||||
final ValueNotifier<bool> showAppBar;
|
||||
final ValueNotifier<bool> displayAppBar;
|
||||
final StreamSink<Offset> pointerMoveEventSink;
|
||||
final Size bottomAppBarSize;
|
||||
|
||||
final bool tabInFullScreen;
|
||||
|
||||
const _Browser({
|
||||
required this.overlayController,
|
||||
required this.sheetController,
|
||||
required this.showAppBar,
|
||||
required this.displayAppBar,
|
||||
required this.tabInFullScreen,
|
||||
required this.pointerMoveEventSink,
|
||||
required this.bottomAppBarSize,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -363,6 +433,7 @@ class _Browser extends HookConsumerWidget {
|
||||
child: _ViewUrlSheet(
|
||||
initialTabState: parameter.tabState,
|
||||
maxChildSize: relativeSafeArea,
|
||||
bottomAppBarSize: bottomAppBarSize,
|
||||
),
|
||||
),
|
||||
};
|
||||
@@ -453,7 +524,7 @@ class _Browser extends HookConsumerWidget {
|
||||
|
||||
//Make sure app bar is visible
|
||||
if (!ref.read(tabBarDismissableControllerProvider)) {
|
||||
showAppBar.value = true;
|
||||
displayAppBar.value = true;
|
||||
}
|
||||
|
||||
if (tabState?.isLoading == true) {
|
||||
@@ -530,6 +601,7 @@ class _Browser extends HookConsumerWidget {
|
||||
child: _BrowserView(
|
||||
sheetDisplayed: sheetController.value != null,
|
||||
isFullscreen: tabInFullScreen,
|
||||
pointerMoveEventSink: pointerMoveEventSink,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -542,10 +614,12 @@ class _Browser extends HookConsumerWidget {
|
||||
class _BrowserView extends StatelessWidget {
|
||||
final bool sheetDisplayed;
|
||||
final bool isFullscreen;
|
||||
final StreamSink<Offset>? pointerMoveEventSink;
|
||||
|
||||
const _BrowserView({
|
||||
required this.sheetDisplayed,
|
||||
required this.isFullscreen,
|
||||
this.pointerMoveEventSink,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -559,7 +633,7 @@ class _BrowserView extends StatelessWidget {
|
||||
left: !isFullscreen,
|
||||
child: Stack(
|
||||
children: [
|
||||
const BrowserView(),
|
||||
BrowserView(pointerMoveEventSink: pointerMoveEventSink),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
@@ -627,8 +701,13 @@ class _BrowserView extends StatelessWidget {
|
||||
class _ViewUrlSheet extends HookConsumerWidget {
|
||||
final double maxChildSize;
|
||||
final TabState initialTabState;
|
||||
final Size bottomAppBarSize;
|
||||
|
||||
const _ViewUrlSheet({required this.initialTabState, this.maxChildSize = 1.0});
|
||||
const _ViewUrlSheet({
|
||||
required this.initialTabState,
|
||||
required this.bottomAppBarSize,
|
||||
this.maxChildSize = 1.0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -666,6 +745,7 @@ class _ViewUrlSheet extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
initialHeight: initialHeight,
|
||||
bottomAppBarHeight: bottomAppBarSize.height,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+224
-87
@@ -44,35 +44,114 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
|
||||
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/providers.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/hooks/cached_future.dart';
|
||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||
import 'package:weblibre/presentation/icons/tor_icons.dart';
|
||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
class BrowserTopAppBar extends HookConsumerWidget {
|
||||
final bool showMainToolbar;
|
||||
final bool showContextualToolbar;
|
||||
final bool showQuickTabSwitcherBar;
|
||||
|
||||
const BrowserBottomAppBar({
|
||||
late final BrowserTabBar _tabBar;
|
||||
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
||||
|
||||
BrowserTopAppBar({
|
||||
required this.showMainToolbar,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
}) {
|
||||
_tabBar = BrowserTabBar(
|
||||
showMainToolbar: showMainToolbar,
|
||||
displayedSheet: null,
|
||||
showContextualToolbar: false,
|
||||
showQuickTabSwitcherBar: false,
|
||||
showMainToolbarNavigationButton: !showContextualToolbar,
|
||||
showMainToolbarTabsCount: !showContextualToolbar,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return SafeArea(
|
||||
child: SizedBox(height: preferredSize.height, child: _tabBar),
|
||||
);
|
||||
}
|
||||
|
||||
Size get preferredSize => _size;
|
||||
}
|
||||
|
||||
class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
final bool showMainToolbar;
|
||||
final bool showContextualToolbar;
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final Sheet? displayedSheet;
|
||||
|
||||
late final BrowserTabBar _tabBar;
|
||||
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
||||
|
||||
BrowserBottomAppBar({
|
||||
required this.showMainToolbar,
|
||||
required this.displayedSheet,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
});
|
||||
}) {
|
||||
_tabBar = BrowserTabBar(
|
||||
displayedSheet: displayedSheet,
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
showMainToolbarNavigationButton: !showContextualToolbar,
|
||||
showMainToolbarTabsCount: !showContextualToolbar,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BottomAppBar(
|
||||
height: _size.height,
|
||||
padding: EdgeInsets.zero,
|
||||
child: _tabBar,
|
||||
);
|
||||
}
|
||||
|
||||
Size get preferredSize => _size;
|
||||
}
|
||||
|
||||
class BrowserTabBar extends HookConsumerWidget {
|
||||
final bool showMainToolbar;
|
||||
final bool showContextualToolbar;
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final Sheet? displayedSheet;
|
||||
|
||||
final bool showMainToolbarTabsCount;
|
||||
final bool showMainToolbarNavigationButton;
|
||||
|
||||
const BrowserTabBar({
|
||||
required this.showMainToolbar,
|
||||
required this.displayedSheet,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.showMainToolbarTabsCount,
|
||||
required this.showMainToolbarNavigationButton,
|
||||
});
|
||||
|
||||
static const contextualToolabarHeight = 54.0;
|
||||
static const quickTabSwitcherHeight = 48.0;
|
||||
|
||||
bool get displayAppBar =>
|
||||
!showContextualToolbar || displayedSheet is! ViewTabsSheet;
|
||||
showMainToolbar &&
|
||||
(!showContextualToolbar || displayedSheet is! ViewTabsSheet);
|
||||
|
||||
bool get displayQuickTabSwitcher =>
|
||||
showQuickTabSwitcherBar && displayedSheet is! ViewTabsSheet;
|
||||
@@ -113,67 +192,68 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
|
||||
final dragStartPosition = useRef(Offset.zero);
|
||||
|
||||
return BottomAppBar(
|
||||
height: getToolbarHeight(),
|
||||
padding: EdgeInsets.zero,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (displayedSheet case EditUrlSheet()) {
|
||||
ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
|
||||
return;
|
||||
}
|
||||
final toolbarHeight = useMemoized(() => getToolbarHeight());
|
||||
|
||||
final tabState = ref.read(selectedTabStateProvider);
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (displayedSheet case EditUrlSheet()) {
|
||||
ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tabState != null) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.show(EditUrlSheet(tabState: tabState));
|
||||
}
|
||||
},
|
||||
onHorizontalDragStart: (details) {
|
||||
dragStartPosition.value = details.globalPosition;
|
||||
},
|
||||
onHorizontalDragEnd: (details) async {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
final tabState = ref.read(selectedTabStateProvider);
|
||||
|
||||
if (distance.dx.abs() > 50 && distance.dy.abs() < 20) {
|
||||
final selectedTab = ref.read(selectedTabProvider);
|
||||
final setting = await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetchSettings();
|
||||
if (tabState != null) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.show(EditUrlSheet(tabState: tabState));
|
||||
}
|
||||
},
|
||||
onHorizontalDragStart: (details) {
|
||||
dragStartPosition.value = details.globalPosition;
|
||||
},
|
||||
onHorizontalDragEnd: (details) async {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
|
||||
if (selectedTab != null) {
|
||||
switch (setting.tabBarSwipeAction) {
|
||||
case TabBarSwipeAction.switchLastOpened:
|
||||
if (distance.dx.abs() > 50 && distance.dy.abs() < 20) {
|
||||
final selectedTab = ref.read(selectedTabProvider);
|
||||
final setting = await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetchSettings();
|
||||
|
||||
if (selectedTab != null) {
|
||||
switch (setting.tabBarSwipeAction) {
|
||||
case TabBarSwipeAction.switchLastOpened:
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectPreviouslyOpenedTab(selectedTab);
|
||||
case TabBarSwipeAction.navigateOrderedTabs:
|
||||
if (distance.dx < 0) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectPreviouslyOpenedTab(selectedTab);
|
||||
case TabBarSwipeAction.navigateOrderedTabs:
|
||||
if (distance.dx < 0) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectPreviousTab(selectedTab);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectNextTab(selectedTab);
|
||||
}
|
||||
}
|
||||
.selectPreviousTab(selectedTab);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectNextTab(selectedTab);
|
||||
}
|
||||
}
|
||||
} else if (distance.dy < 20 && distance.dx.abs() < 15) {
|
||||
ref.read(tabBarDismissableControllerProvider.notifier).dismiss();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (showQuickTabSwitcherBar)
|
||||
Visibility(
|
||||
visible: displayQuickTabSwitcher,
|
||||
maintainState: true,
|
||||
child: QuickTabSwitcher(),
|
||||
),
|
||||
} else if (distance.dy < (toolbarHeight / 3) &&
|
||||
distance.dx.abs() < 15) {
|
||||
ref.read(tabBarDismissableControllerProvider.notifier).dismiss();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (showQuickTabSwitcherBar)
|
||||
Visibility(
|
||||
visible: displayQuickTabSwitcher,
|
||||
maintainState: true,
|
||||
child: QuickTabSwitcher(),
|
||||
),
|
||||
if (showMainToolbar)
|
||||
Visibility(
|
||||
visible: displayAppBar,
|
||||
maintainState: true,
|
||||
@@ -269,29 +349,40 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!showContextualToolbar)
|
||||
if (showMainToolbarTabsCount)
|
||||
TabsCountButton(
|
||||
selectedTabId: selectedTabId,
|
||||
displayedSheet: displayedSheet,
|
||||
showLongPressMenu: true,
|
||||
),
|
||||
if (!showContextualToolbar)
|
||||
NavigationMenuButton(selectedTabId: selectedTabId),
|
||||
if (showMainToolbarNavigationButton)
|
||||
NavigationMenuButton(
|
||||
selectedTabId: selectedTabId,
|
||||
showNavigationButtons: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showContextualToolbar)
|
||||
ContextualToolbar(
|
||||
selectedTabId: selectedTabId,
|
||||
displayedSheet: displayedSheet,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (showContextualToolbar)
|
||||
ContextualToolbar(
|
||||
selectedTabId: selectedTabId,
|
||||
displayedSheet: displayedSheet,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef _QuickTabItem = ({
|
||||
Color? color,
|
||||
String id,
|
||||
bool isPrivate,
|
||||
bool isHistory,
|
||||
String title,
|
||||
Uri url,
|
||||
});
|
||||
|
||||
class ContextualToolbar extends HookConsumerWidget {
|
||||
const ContextualToolbar({
|
||||
super.key,
|
||||
@@ -333,7 +424,10 @@ class ContextualToolbar extends HookConsumerWidget {
|
||||
displayedSheet: displayedSheet,
|
||||
showLongPressMenu: false,
|
||||
),
|
||||
NavigationMenuButton(selectedTabId: selectedTabId),
|
||||
NavigationMenuButton(
|
||||
selectedTabId: selectedTabId,
|
||||
showNavigationButtons: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -342,7 +436,16 @@ class ContextualToolbar extends HookConsumerWidget {
|
||||
class QuickTabSwitcher extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tabStates = ref.watch(fifoTabStatesProvider).value;
|
||||
final tabStates = ref.watch(fifoTabStatesProvider);
|
||||
final historyAsync = useCachedFuture(() {
|
||||
if (tabStates.value.length <= 1) {
|
||||
return ref
|
||||
.read(historyRepositoryProvider.notifier)
|
||||
.getVisitsPaginated(count: 25);
|
||||
}
|
||||
|
||||
return Future.value(<VisitInfo>[]);
|
||||
}, [tabStates.value.length <= 1]);
|
||||
|
||||
final chipScrollController = useScrollController();
|
||||
|
||||
@@ -351,19 +454,19 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
width: double.maxFinite,
|
||||
child: SelectableChips<FifoTab, FifoTab, String>(
|
||||
child: SelectableChips<_QuickTabItem, _QuickTabItem, String>(
|
||||
enableDelete: false,
|
||||
scrollController: chipScrollController,
|
||||
itemId: (item) => item.$1.id,
|
||||
itemId: (item) => item.id,
|
||||
itemLabel: (item) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 64),
|
||||
child: Text(item.$1.titleOrAuthority),
|
||||
child: Text(item.title),
|
||||
),
|
||||
if (item.$1.isPrivate)
|
||||
if (item.isPrivate)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
child: Icon(
|
||||
@@ -372,23 +475,57 @@ class QuickTabSwitcher extends HookConsumerWidget {
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
if (item.isHistory)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
child: Icon(MdiIcons.history, size: 20),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
itemAvatar: (item) => UrlIcon([item.$1.url], iconSize: 16),
|
||||
itemBackgroundColor: (item) => item.$2?.color.withValues(alpha: 0.5),
|
||||
itemAvatar: (item) => UrlIcon([item.url], iconSize: 16),
|
||||
itemBackgroundColor: (item) => item.color?.withValues(alpha: 0.5),
|
||||
onSelected: (item) async {
|
||||
final animation = chipScrollController.animateTo(
|
||||
0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutBack,
|
||||
);
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectTab(item.$1.id);
|
||||
if (item.isHistory) {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: item.url, private: false);
|
||||
} else {
|
||||
await ref.read(tabRepositoryProvider.notifier).selectTab(item.id);
|
||||
}
|
||||
await animation;
|
||||
},
|
||||
availableItems: tabStates.skip(1),
|
||||
availableItems: tabStates.value
|
||||
.map(
|
||||
(state) => (
|
||||
id: state.$1.id,
|
||||
title: state.$1.titleOrAuthority,
|
||||
isPrivate: state.$1.isPrivate,
|
||||
isHistory: false,
|
||||
url: state.$1.url,
|
||||
color: state.$2?.color,
|
||||
),
|
||||
)
|
||||
.skip(1)
|
||||
.followedBy(
|
||||
(historyAsync.data ?? []).map((state) {
|
||||
final url = Uri.parse(state.url);
|
||||
|
||||
return (
|
||||
id: state.url,
|
||||
title: state.title ?? url.authority,
|
||||
isPrivate: false,
|
||||
isHistory: true,
|
||||
url: url,
|
||||
color: null,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -430,18 +567,18 @@ class ShareMenuButton extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
class NavigationMenuButton extends HookConsumerWidget {
|
||||
const NavigationMenuButton({super.key, required this.selectedTabId});
|
||||
|
||||
final String? selectedTabId;
|
||||
final bool showNavigationButtons;
|
||||
|
||||
const NavigationMenuButton({
|
||||
super.key,
|
||||
required this.selectedTabId,
|
||||
required this.showNavigationButtons,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final addonService = ref.watch(addonServiceProvider);
|
||||
final showContextualAppBar = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarShowContextualBar,
|
||||
),
|
||||
);
|
||||
|
||||
final hamburgerMenuController = useMenuController();
|
||||
|
||||
@@ -674,7 +811,7 @@ class NavigationMenuButton extends HookConsumerWidget {
|
||||
child: const Text('Reload'),
|
||||
),
|
||||
if (selectedTabId != null) const Divider(),
|
||||
if (selectedTabId != null && !showContextualAppBar)
|
||||
if (selectedTabId != null && showNavigationButtons)
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final history = ref.watch(
|
||||
|
||||
+85
-69
@@ -44,6 +44,7 @@ import 'package:weblibre/features/geckoview/features/browser/domain/providers/li
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart';
|
||||
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
|
||||
import 'package:weblibre/features/geckoview/features/preferences/data/repositories/preference_observer.dart';
|
||||
import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
@@ -59,11 +60,13 @@ class BrowserView extends StatefulHookConsumerWidget {
|
||||
final Duration screenshotPeriod;
|
||||
final Duration suggestionTimeout;
|
||||
final Future<void> Function()? postInitializationStep;
|
||||
final StreamSink<Offset>? pointerMoveEventSink;
|
||||
|
||||
const BrowserView({
|
||||
this.screenshotPeriod = const Duration(seconds: 10),
|
||||
this.suggestionTimeout = const Duration(seconds: 30),
|
||||
this.postInitializationStep,
|
||||
this.pointerMoveEventSink,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -106,9 +109,9 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
|
||||
if (settings.historyAutoCleanInterval > Duration.zero) {
|
||||
await GeckoHistoryService().deleteVisitsBetween(
|
||||
DateTime(0),
|
||||
DateTime.now().subtract(settings.historyAutoCleanInterval),
|
||||
);
|
||||
DateTime(0),
|
||||
DateTime.now().subtract(settings.historyAutoCleanInterval),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -216,74 +219,87 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
},
|
||||
);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
GeckoView(
|
||||
preInitializationStep: () async {
|
||||
await ref
|
||||
.read(eventServiceProvider)
|
||||
.viewReadyStateEvents
|
||||
.firstWhere((state) => state == true)
|
||||
.timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
logger.e(
|
||||
'Browser fragement not reported ready, trying to intitialize anyways',
|
||||
);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
},
|
||||
postInitializationStep: () async {
|
||||
await widget.postInitializationStep?.call();
|
||||
|
||||
if (!initializationCompleter.isCompleted) {
|
||||
const quickActions = QuickActions();
|
||||
|
||||
//Debounce: https://github.com/flutter/flutter/issues/131121
|
||||
DateTime? lastAction;
|
||||
await quickActions.initialize((type) async {
|
||||
if (lastAction == null ||
|
||||
DateTime.now().difference(lastAction!) >
|
||||
const Duration(seconds: 5)) {
|
||||
if (type == 'new_tab') {
|
||||
lastAction = DateTime.now();
|
||||
|
||||
final router = await ref.read(routerProvider.future);
|
||||
const route = SearchRoute(tabType: TabType.regular);
|
||||
|
||||
await router.push(route.location);
|
||||
} else if (type == 'new_private_tab') {
|
||||
lastAction = DateTime.now();
|
||||
|
||||
final router = await ref.read(routerProvider.future);
|
||||
const route = SearchRoute(tabType: TabType.private);
|
||||
|
||||
await router.push(route.location);
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await quickActions.setShortcutItems([
|
||||
//TODO: add icons
|
||||
const ShortcutItem(type: 'new_tab', localizedTitle: 'New Tab'),
|
||||
const ShortcutItem(
|
||||
type: 'new_private_tab',
|
||||
localizedTitle: 'New Private Tab',
|
||||
),
|
||||
]);
|
||||
|
||||
initializationCompleter.complete();
|
||||
return Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerMove: (widget.pointerMoveEventSink != null)
|
||||
? (event) {
|
||||
if (event.down) {
|
||||
widget.pointerMoveEventSink!.add(event.localDelta);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!hasTab)
|
||||
Positioned.fill(
|
||||
child: SizedBox.expand(child: Container(color: Colors.grey[800])),
|
||||
: null,
|
||||
child: Stack(
|
||||
children: [
|
||||
GeckoView(
|
||||
preInitializationStep: () async {
|
||||
await ref
|
||||
.read(eventServiceProvider)
|
||||
.viewReadyStateEvents
|
||||
.firstWhere((state) => state == true)
|
||||
.timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
logger.e(
|
||||
'Browser fragement not reported ready, trying to intitialize anyways',
|
||||
);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
},
|
||||
postInitializationStep: () async {
|
||||
await widget.postInitializationStep?.call();
|
||||
|
||||
if (!initializationCompleter.isCompleted) {
|
||||
const quickActions = QuickActions();
|
||||
|
||||
//Debounce: https://github.com/flutter/flutter/issues/131121
|
||||
DateTime? lastAction;
|
||||
await quickActions.initialize((type) async {
|
||||
if (lastAction == null ||
|
||||
DateTime.now().difference(lastAction!) >
|
||||
const Duration(seconds: 5)) {
|
||||
if (type == 'new_tab') {
|
||||
lastAction = DateTime.now();
|
||||
|
||||
final router = await ref.read(routerProvider.future);
|
||||
const route = SearchRoute(tabType: TabType.regular);
|
||||
|
||||
await router.push(route.location);
|
||||
} else if (type == 'new_private_tab') {
|
||||
lastAction = DateTime.now();
|
||||
|
||||
final router = await ref.read(routerProvider.future);
|
||||
const route = SearchRoute(tabType: TabType.private);
|
||||
|
||||
await router.push(route.location);
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await quickActions.setShortcutItems([
|
||||
//TODO: add icons
|
||||
const ShortcutItem(
|
||||
type: 'new_tab',
|
||||
localizedTitle: 'New Tab',
|
||||
),
|
||||
const ShortcutItem(
|
||||
type: 'new_private_tab',
|
||||
localizedTitle: 'New Private Tab',
|
||||
),
|
||||
]);
|
||||
|
||||
initializationCompleter.complete();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
if (!hasTab)
|
||||
Positioned.fill(
|
||||
child: SizedBox.expand(child: Container(color: Colors.grey[800])),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -48,6 +48,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
||||
final DraggableScrollableController draggableScrollableController;
|
||||
final VoidCallback onClose;
|
||||
final double initialHeight;
|
||||
final double bottomAppBarHeight;
|
||||
|
||||
const ViewTabSheetWidget({
|
||||
required this.initialTabState,
|
||||
@@ -55,6 +56,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
||||
required this.draggableScrollableController,
|
||||
required this.onClose,
|
||||
required this.initialHeight,
|
||||
required this.bottomAppBarHeight,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -79,7 +81,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
||||
final totalHeight =
|
||||
headerBox.size.height +
|
||||
textBox.size.height +
|
||||
kToolbarHeight +
|
||||
bottomAppBarHeight +
|
||||
MediaQuery.of(context).viewInsets.bottom;
|
||||
|
||||
final relative = (totalHeight / MediaQuery.of(context).size.height)
|
||||
|
||||
Reference in New Issue
Block a user