app bar positioning

This commit is contained in:
Fabian Freund
2025-12-22 21:00:02 +01:00
parent a90f18d9c8
commit 5a46751834
11 changed files with 540 additions and 290 deletions
@@ -300,13 +300,13 @@ AsyncValue<String?> selectedTabContainerId(Ref ref) {
return const AsyncData(null); return const AsyncData(null);
} }
@Riverpod() // @Riverpod()
Stream<int> tabScrollY(Ref ref, String? tabId, Duration sampleTime) { // Stream<int> tabScrollY(Ref ref, String? tabId, Duration sampleTime) {
final eventService = ref.watch(eventServiceProvider); // final eventService = ref.watch(eventServiceProvider);
return eventService.scrollEvent // return eventService.scrollEvent
.where((event) => event.tabId == tabId) // .where((event) => event.tabId == tabId)
.sampleTime(sampleTime) // .sampleTime(sampleTime)
.map((event) => event.scrollY) // .map((event) => event.scrollY)
.asBroadcastStream(); // .asBroadcastStream();
} // }
@@ -337,72 +337,3 @@ final class SelectedTabContainerIdProvider
String _$selectedTabContainerIdHash() => String _$selectedTabContainerIdHash() =>
r'07899d29f69654d3b314d0da945ad402b1003b41'; 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/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/find_in_page/presentation/widgets/find_in_page.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.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/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
class _TabBar extends HookConsumerWidget { class _TabBar extends HookConsumerWidget {
final bool showMainToolbar;
final bool showContextualToolbar; final bool showContextualToolbar;
final bool showQuickTabSwitcherBar; final bool showQuickTabSwitcherBar;
final ValueNotifier<bool> showAppBar; final ValueNotifier<bool> displayAppBar;
final ValueNotifier<PersistentBottomSheetController?> sheetController; final ValueNotifier<PersistentBottomSheetController?> sheetController;
final Stream<Offset>? pointerMoveEvents;
final TabBarPosition tabBarPosition;
const _TabBar({ const _TabBar({
required this.showMainToolbar,
required this.showContextualToolbar, required this.showContextualToolbar,
required this.showQuickTabSwitcherBar, required this.showQuickTabSwitcherBar,
required this.showAppBar, required this.displayAppBar,
required this.sheetController, required this.sheetController,
required this.tabBarPosition,
required this.pointerMoveEvents,
}); });
@override @override
@@ -75,29 +82,43 @@ class _TabBar extends HookConsumerWidget {
selectedTabStateProvider.select((value) => value?.isFullScreen ?? false), selectedTabStateProvider.select((value) => value?.isFullScreen ?? false),
); );
final autoHideTabBar = ref.watch( final autoHideTabBar = switch (tabBarPosition) {
generalSettingsWithDefaultsProvider.select( TabBarPosition.top => false,
(value) => value.autoHideTabBar, TabBarPosition.bottom => ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.autoHideTabBar,
),
), ),
); };
final appBarVisible = tabBarPosition == TabBarPosition.top
? !ref.watch(tabBarDismissableControllerProvider)
: useValueListenable(displayAppBar);
if (!autoHideTabBar) { if (!autoHideTabBar) {
return Visibility( return Visibility(
visible: !tabInFullScreen, visible: !tabInFullScreen && appBarVisible,
child: BrowserBottomAppBar( child: switch (tabBarPosition) {
displayedSheet: displayedSheet, TabBarPosition.top => BrowserTopAppBar(
showContextualToolbar: showContextualToolbar, showMainToolbar: showMainToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar, showContextualToolbar: showContextualToolbar,
), showQuickTabSwitcherBar: showQuickTabSwitcherBar,
),
TabBarPosition.bottom => BrowserBottomAppBar(
displayedSheet: displayedSheet,
showMainToolbar: showMainToolbar,
showContextualToolbar: showContextualToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
),
},
); );
} }
final appBarVisible = useValueListenable(showAppBar);
final diffAcc = useRef(0.0); final diffAcc = useRef(0.0);
void resetHiddenState() { void resetHiddenState() {
if (!ref.read(tabBarDismissableControllerProvider)) { if (!ref.read(tabBarDismissableControllerProvider)) {
showAppBar.value = true; displayAppBar.value = true;
} }
diffAcc.value = 0.0; diffAcc.value = 0.0;
@@ -137,12 +158,10 @@ class _TabBar extends HookConsumerWidget {
} }
}); });
ref.listen(tabScrollYProvider(tabId, const Duration(milliseconds: 50)), ( useOnStreamChange(
previous, pointerMoveEvents,
next, onData: (event) {
) { final diff = event.dy;
if (previous?.value != null && next.value != null) {
final diff = previous!.value! - next.value!;
if (diff < 0) { if (diff < 0) {
if (diffAcc.value > 0) { if (diffAcc.value > 0) {
diffAcc.value = 0.0; diffAcc.value = 0.0;
@@ -150,7 +169,7 @@ class _TabBar extends HookConsumerWidget {
diffAcc.value += diff; diffAcc.value += diff;
if (diffAcc.value.abs() > kToolbarHeight * 1.5) { if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
showAppBar.value = false; displayAppBar.value = false;
} }
} else if (diff > 0) { } else if (diff > 0) {
if (diffAcc.value < 0) { if (diffAcc.value < 0) {
@@ -162,18 +181,26 @@ class _TabBar extends HookConsumerWidget {
resetHiddenState(); resetHiddenState();
} }
} }
} },
}); );
return Visibility( return Visibility(
visible: visible:
sheetController.value != null || (!tabInFullScreen && appBarVisible), sheetController.value != null || (!tabInFullScreen && appBarVisible),
maintainState: true, maintainState: true,
child: BrowserBottomAppBar( child: switch (tabBarPosition) {
displayedSheet: displayedSheet, TabBarPosition.top => BrowserTopAppBar(
showContextualToolbar: showContextualToolbar, showMainToolbar: showMainToolbar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar, 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 overlayController = useOverlayPortalController();
final showContextualAppBar = ref.watch( final tabBarPosition = ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.tabBarPosition,
),
);
final showContextualToolbar = ref.watch(
generalSettingsWithDefaultsProvider.select( generalSettingsWithDefaultsProvider.select(
(value) => value.tabBarShowContextualBar, (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) { ref.listen(tabBarDismissableControllerProvider, (previous, next) {
showAppBar.value = !next; displayAppBar.value = !next;
if (tabBarPosition == TabBarPosition.bottom) {
removeTopAppBar.value = next;
}
}); });
ref.listen(overlayControllerProvider, (previous, next) { ref.listen(overlayControllerProvider, (previous, next) {
@@ -244,6 +281,8 @@ class BrowserScreen extends HookConsumerWidget {
final sheetController = useState<PersistentBottomSheetController?>(null); final sheetController = useState<PersistentBottomSheetController?>(null);
final pointerMoveEventsController = useStreamController<Offset>();
return PopScope( return PopScope(
//We need this for BackButtonListener to work downstream //We need this for BackButtonListener to work downstream
//No direct pop result will be handled here //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 //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; return null;
}, },
body: Column( appBar: (tabBarPosition == TabBarPosition.top)
children: [ ? PreferredSize(
_TabBar( preferredSize: BrowserTopAppBar(
showAppBar: showAppBar, showMainToolbar: true,
sheetController: sheetController, showContextualToolbar: showContextualToolbar,
showContextualToolbar: showContextualAppBar, showQuickTabSwitcherBar: showQuickTabSwitcherBar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar, ).preferredSize,
), child: _TabBar(
Expanded( tabBarPosition: TabBarPosition.top,
child: _Browser( showMainToolbar: true,
overlayController: overlayController, displayAppBar: displayAppBar,
sheetController: sheetController, sheetController: sheetController,
showAppBar: showAppBar, showContextualToolbar: showContextualToolbar,
tabInFullScreen: tabInFullScreen, 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(), floatingActionButton: BrowserFab(),
), ),
@@ -286,15 +352,19 @@ class _Browser extends HookConsumerWidget {
final OverlayPortalController overlayController; final OverlayPortalController overlayController;
final ValueNotifier<PersistentBottomSheetController?> sheetController; final ValueNotifier<PersistentBottomSheetController?> sheetController;
final ValueNotifier<bool> showAppBar; final ValueNotifier<bool> displayAppBar;
final StreamSink<Offset> pointerMoveEventSink;
final Size bottomAppBarSize;
final bool tabInFullScreen; final bool tabInFullScreen;
const _Browser({ const _Browser({
required this.overlayController, required this.overlayController,
required this.sheetController, required this.sheetController,
required this.showAppBar, required this.displayAppBar,
required this.tabInFullScreen, required this.tabInFullScreen,
required this.pointerMoveEventSink,
required this.bottomAppBarSize,
}); });
@override @override
@@ -363,6 +433,7 @@ class _Browser extends HookConsumerWidget {
child: _ViewUrlSheet( child: _ViewUrlSheet(
initialTabState: parameter.tabState, initialTabState: parameter.tabState,
maxChildSize: relativeSafeArea, maxChildSize: relativeSafeArea,
bottomAppBarSize: bottomAppBarSize,
), ),
), ),
}; };
@@ -453,7 +524,7 @@ class _Browser extends HookConsumerWidget {
//Make sure app bar is visible //Make sure app bar is visible
if (!ref.read(tabBarDismissableControllerProvider)) { if (!ref.read(tabBarDismissableControllerProvider)) {
showAppBar.value = true; displayAppBar.value = true;
} }
if (tabState?.isLoading == true) { if (tabState?.isLoading == true) {
@@ -530,6 +601,7 @@ class _Browser extends HookConsumerWidget {
child: _BrowserView( child: _BrowserView(
sheetDisplayed: sheetController.value != null, sheetDisplayed: sheetController.value != null,
isFullscreen: tabInFullScreen, isFullscreen: tabInFullScreen,
pointerMoveEventSink: pointerMoveEventSink,
), ),
), ),
), ),
@@ -542,10 +614,12 @@ class _Browser extends HookConsumerWidget {
class _BrowserView extends StatelessWidget { class _BrowserView extends StatelessWidget {
final bool sheetDisplayed; final bool sheetDisplayed;
final bool isFullscreen; final bool isFullscreen;
final StreamSink<Offset>? pointerMoveEventSink;
const _BrowserView({ const _BrowserView({
required this.sheetDisplayed, required this.sheetDisplayed,
required this.isFullscreen, required this.isFullscreen,
this.pointerMoveEventSink,
}); });
@override @override
@@ -559,7 +633,7 @@ class _BrowserView extends StatelessWidget {
left: !isFullscreen, left: !isFullscreen,
child: Stack( child: Stack(
children: [ children: [
const BrowserView(), BrowserView(pointerMoveEventSink: pointerMoveEventSink),
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@@ -627,8 +701,13 @@ class _BrowserView extends StatelessWidget {
class _ViewUrlSheet extends HookConsumerWidget { class _ViewUrlSheet extends HookConsumerWidget {
final double maxChildSize; final double maxChildSize;
final TabState initialTabState; 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 @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -666,6 +745,7 @@ class _ViewUrlSheet extends HookConsumerWidget {
} }
}, },
initialHeight: initialHeight, initialHeight: initialHeight,
bottomAppBarHeight: bottomAppBarSize.height,
), ),
); );
}, },
@@ -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_creation_menu.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_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/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/controllers/readerable.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.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/tor/domain/services/tor_proxy.dart';
import 'package:weblibre/features/user/data/models/general_settings.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/providers.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.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/hooks/menu_controller.dart';
import 'package:weblibre/presentation/icons/tor_icons.dart'; import 'package:weblibre/presentation/icons/tor_icons.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; 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 showContextualToolbar;
final bool showQuickTabSwitcherBar; 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.displayedSheet,
required this.showContextualToolbar, required this.showContextualToolbar,
required this.showQuickTabSwitcherBar, 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 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 contextualToolabarHeight = 54.0;
static const quickTabSwitcherHeight = 48.0; static const quickTabSwitcherHeight = 48.0;
bool get displayAppBar => bool get displayAppBar =>
!showContextualToolbar || displayedSheet is! ViewTabsSheet; showMainToolbar &&
(!showContextualToolbar || displayedSheet is! ViewTabsSheet);
bool get displayQuickTabSwitcher => bool get displayQuickTabSwitcher =>
showQuickTabSwitcherBar && displayedSheet is! ViewTabsSheet; showQuickTabSwitcherBar && displayedSheet is! ViewTabsSheet;
@@ -113,67 +192,68 @@ class BrowserBottomAppBar extends HookConsumerWidget {
final dragStartPosition = useRef(Offset.zero); final dragStartPosition = useRef(Offset.zero);
return BottomAppBar( final toolbarHeight = useMemoized(() => getToolbarHeight());
height: getToolbarHeight(),
padding: EdgeInsets.zero,
child: GestureDetector(
onTap: () {
if (displayedSheet case EditUrlSheet()) {
ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
return;
}
final tabState = ref.read(selectedTabStateProvider); return GestureDetector(
onTap: () {
if (displayedSheet case EditUrlSheet()) {
ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
return;
}
if (tabState != null) { final tabState = ref.read(selectedTabStateProvider);
ref
.read(bottomSheetControllerProvider.notifier)
.show(EditUrlSheet(tabState: tabState));
}
},
onHorizontalDragStart: (details) {
dragStartPosition.value = details.globalPosition;
},
onHorizontalDragEnd: (details) async {
final distance = dragStartPosition.value - details.globalPosition;
if (distance.dx.abs() > 50 && distance.dy.abs() < 20) { if (tabState != null) {
final selectedTab = ref.read(selectedTabProvider); ref
final setting = await ref .read(bottomSheetControllerProvider.notifier)
.read(generalSettingsRepositoryProvider.notifier) .show(EditUrlSheet(tabState: tabState));
.fetchSettings(); }
},
onHorizontalDragStart: (details) {
dragStartPosition.value = details.globalPosition;
},
onHorizontalDragEnd: (details) async {
final distance = dragStartPosition.value - details.globalPosition;
if (selectedTab != null) { if (distance.dx.abs() > 50 && distance.dy.abs() < 20) {
switch (setting.tabBarSwipeAction) { final selectedTab = ref.read(selectedTabProvider);
case TabBarSwipeAction.switchLastOpened: 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 await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.selectPreviouslyOpenedTab(selectedTab); .selectPreviousTab(selectedTab);
case TabBarSwipeAction.navigateOrderedTabs: } else {
if (distance.dx < 0) { await ref
await ref .read(tabRepositoryProvider.notifier)
.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();
} }
}, } else if (distance.dy < (toolbarHeight / 3) &&
child: Column( distance.dx.abs() < 15) {
mainAxisSize: MainAxisSize.min, ref.read(tabBarDismissableControllerProvider.notifier).dismiss();
children: [ }
if (showQuickTabSwitcherBar) },
Visibility( child: Column(
visible: displayQuickTabSwitcher, mainAxisSize: MainAxisSize.min,
maintainState: true, children: [
child: QuickTabSwitcher(), if (showQuickTabSwitcherBar)
), Visibility(
visible: displayQuickTabSwitcher,
maintainState: true,
child: QuickTabSwitcher(),
),
if (showMainToolbar)
Visibility( Visibility(
visible: displayAppBar, visible: displayAppBar,
maintainState: true, maintainState: true,
@@ -269,29 +349,40 @@ class BrowserBottomAppBar extends HookConsumerWidget {
), ),
), ),
), ),
if (!showContextualToolbar) if (showMainToolbarTabsCount)
TabsCountButton( TabsCountButton(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
displayedSheet: displayedSheet, displayedSheet: displayedSheet,
showLongPressMenu: true, showLongPressMenu: true,
), ),
if (!showContextualToolbar) if (showMainToolbarNavigationButton)
NavigationMenuButton(selectedTabId: selectedTabId), NavigationMenuButton(
selectedTabId: selectedTabId,
showNavigationButtons: true,
),
], ],
), ),
), ),
if (showContextualToolbar) if (showContextualToolbar)
ContextualToolbar( ContextualToolbar(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
displayedSheet: displayedSheet, displayedSheet: displayedSheet,
), ),
], ],
),
), ),
); );
} }
} }
typedef _QuickTabItem = ({
Color? color,
String id,
bool isPrivate,
bool isHistory,
String title,
Uri url,
});
class ContextualToolbar extends HookConsumerWidget { class ContextualToolbar extends HookConsumerWidget {
const ContextualToolbar({ const ContextualToolbar({
super.key, super.key,
@@ -333,7 +424,10 @@ class ContextualToolbar extends HookConsumerWidget {
displayedSheet: displayedSheet, displayedSheet: displayedSheet,
showLongPressMenu: false, showLongPressMenu: false,
), ),
NavigationMenuButton(selectedTabId: selectedTabId), NavigationMenuButton(
selectedTabId: selectedTabId,
showNavigationButtons: false,
),
], ],
); );
} }
@@ -342,7 +436,16 @@ class ContextualToolbar extends HookConsumerWidget {
class QuickTabSwitcher extends HookConsumerWidget { class QuickTabSwitcher extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { 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(); final chipScrollController = useScrollController();
@@ -351,19 +454,19 @@ class QuickTabSwitcher extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 48, height: 48,
width: double.maxFinite, width: double.maxFinite,
child: SelectableChips<FifoTab, FifoTab, String>( child: SelectableChips<_QuickTabItem, _QuickTabItem, String>(
enableDelete: false, enableDelete: false,
scrollController: chipScrollController, scrollController: chipScrollController,
itemId: (item) => item.$1.id, itemId: (item) => item.id,
itemLabel: (item) { itemLabel: (item) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 64), constraints: const BoxConstraints(maxWidth: 64),
child: Text(item.$1.titleOrAuthority), child: Text(item.title),
), ),
if (item.$1.isPrivate) if (item.isPrivate)
const Padding( const Padding(
padding: EdgeInsets.only(left: 8.0), padding: EdgeInsets.only(left: 8.0),
child: Icon( child: Icon(
@@ -372,23 +475,57 @@ class QuickTabSwitcher extends HookConsumerWidget {
size: 20, 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), itemAvatar: (item) => UrlIcon([item.url], iconSize: 16),
itemBackgroundColor: (item) => item.$2?.color.withValues(alpha: 0.5), itemBackgroundColor: (item) => item.color?.withValues(alpha: 0.5),
onSelected: (item) async { onSelected: (item) async {
final animation = chipScrollController.animateTo( final animation = chipScrollController.animateTo(
0, 0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeOutBack, curve: Curves.easeOutBack,
); );
await ref if (item.isHistory) {
.read(tabRepositoryProvider.notifier) await ref
.selectTab(item.$1.id); .read(tabRepositoryProvider.notifier)
.addTab(url: item.url, private: false);
} else {
await ref.read(tabRepositoryProvider.notifier).selectTab(item.id);
}
await animation; 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 { class NavigationMenuButton extends HookConsumerWidget {
const NavigationMenuButton({super.key, required this.selectedTabId});
final String? selectedTabId; final String? selectedTabId;
final bool showNavigationButtons;
const NavigationMenuButton({
super.key,
required this.selectedTabId,
required this.showNavigationButtons,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final addonService = ref.watch(addonServiceProvider); final addonService = ref.watch(addonServiceProvider);
final showContextualAppBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.tabBarShowContextualBar,
),
);
final hamburgerMenuController = useMenuController(); final hamburgerMenuController = useMenuController();
@@ -674,7 +811,7 @@ class NavigationMenuButton extends HookConsumerWidget {
child: const Text('Reload'), child: const Text('Reload'),
), ),
if (selectedTabId != null) const Divider(), if (selectedTabId != null) const Divider(),
if (selectedTabId != null && !showContextualAppBar) if (selectedTabId != null && showNavigationButtons)
Consumer( Consumer(
builder: (context, ref, child) { builder: (context, ref, child) {
final history = ref.watch( final history = ref.watch(
@@ -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/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/engine_settings_replication.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/services/proxy_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/geckoview/features/preferences/data/repositories/preference_observer.dart';
import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart'; import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart'; import 'package:weblibre/features/user/data/models/general_settings.dart';
@@ -59,11 +60,13 @@ class BrowserView extends StatefulHookConsumerWidget {
final Duration screenshotPeriod; final Duration screenshotPeriod;
final Duration suggestionTimeout; final Duration suggestionTimeout;
final Future<void> Function()? postInitializationStep; final Future<void> Function()? postInitializationStep;
final StreamSink<Offset>? pointerMoveEventSink;
const BrowserView({ const BrowserView({
this.screenshotPeriod = const Duration(seconds: 10), this.screenshotPeriod = const Duration(seconds: 10),
this.suggestionTimeout = const Duration(seconds: 30), this.suggestionTimeout = const Duration(seconds: 30),
this.postInitializationStep, this.postInitializationStep,
this.pointerMoveEventSink,
}); });
@override @override
@@ -106,9 +109,9 @@ class _BrowserViewState extends ConsumerState<BrowserView>
if (settings.historyAutoCleanInterval > Duration.zero) { if (settings.historyAutoCleanInterval > Duration.zero) {
await GeckoHistoryService().deleteVisitsBetween( await GeckoHistoryService().deleteVisitsBetween(
DateTime(0), DateTime(0),
DateTime.now().subtract(settings.historyAutoCleanInterval), DateTime.now().subtract(settings.historyAutoCleanInterval),
); );
} }
}); });
}); });
@@ -216,74 +219,87 @@ class _BrowserViewState extends ConsumerState<BrowserView>
}, },
); );
return Stack( return Listener(
children: [ behavior: HitTestBehavior.translucent,
GeckoView( onPointerMove: (widget.pointerMoveEventSink != null)
preInitializationStep: () async { ? (event) {
await ref if (event.down) {
.read(eventServiceProvider) widget.pointerMoveEventSink!.add(event.localDelta);
.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();
} }
}, : null,
), child: Stack(
if (!hasTab) children: [
Positioned.fill( GeckoView(
child: SizedBox.expand(child: Container(color: Colors.grey[800])), 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])),
),
],
),
); );
} }
@@ -48,6 +48,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
final DraggableScrollableController draggableScrollableController; final DraggableScrollableController draggableScrollableController;
final VoidCallback onClose; final VoidCallback onClose;
final double initialHeight; final double initialHeight;
final double bottomAppBarHeight;
const ViewTabSheetWidget({ const ViewTabSheetWidget({
required this.initialTabState, required this.initialTabState,
@@ -55,6 +56,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
required this.draggableScrollableController, required this.draggableScrollableController,
required this.onClose, required this.onClose,
required this.initialHeight, required this.initialHeight,
required this.bottomAppBarHeight,
}); });
@override @override
@@ -79,7 +81,7 @@ class ViewTabSheetWidget extends HookConsumerWidget {
final totalHeight = final totalHeight =
headerBox.size.height + headerBox.size.height +
textBox.size.height + textBox.size.height +
kToolbarHeight + bottomAppBarHeight +
MediaQuery.of(context).viewInsets.bottom; MediaQuery.of(context).viewInsets.bottom;
final relative = (totalHeight / MediaQuery.of(context).size.height) final relative = (totalHeight / MediaQuery.of(context).size.height)
@@ -362,6 +362,55 @@ class GeneralSettingsScreen extends HookConsumerWidget {
); );
}, },
), ),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Tab Bar Position'),
leading: Icon(MdiIcons.dockWindow),
contentPadding: EdgeInsets.zero,
),
RadioGroup(
groupValue: generalSettings.tabBarPosition,
onChanged: (value) async {
if (value != null) {
await ref
.read(
saveGeneralSettingsControllerProvider.notifier,
)
.save(
(currentSettings) => currentSettings.copyWith
.tabBarPosition(value),
);
}
},
child: const Column(
children: [
RadioListTile.adaptive(
value: TabBarPosition.top,
title: Text('Top'),
subtitle: Text(
'Persistent tab bar without auto-hide',
),
),
RadioListTile.adaptive(
value: TabBarPosition.bottom,
title: Text('Bottom'),
subtitle: Text('Tab bar with auto-hide support'),
),
],
),
),
],
),
),
SwitchListTile.adaptive( SwitchListTile.adaptive(
title: const Text('Show Contextual Tab Bar'), title: const Text('Show Contextual Tab Bar'),
subtitle: const Text( subtitle: const Text(
@@ -38,6 +38,8 @@ enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
enum TabIntentOpenSetting { regular, private, ask } enum TabIntentOpenSetting { regular, private, ask }
enum TabBarPosition { top, bottom }
enum DeleteBrowsingDataType { enum DeleteBrowsingDataType {
tabs('Open tabs'), tabs('Open tabs'),
history('Browsing history'), history('Browsing history'),
@@ -74,6 +76,7 @@ class GeneralSettings with FastEquatable {
final bool tabBarReaderView; final bool tabBarReaderView;
final bool tabBarShowContextualBar; final bool tabBarShowContextualBar;
final bool tabBarShowQuickTabSwitcherBar; final bool tabBarShowQuickTabSwitcherBar;
final TabBarPosition tabBarPosition;
GeneralSettings({ GeneralSettings({
required this.themeMode, required this.themeMode,
@@ -94,6 +97,7 @@ class GeneralSettings with FastEquatable {
required this.tabBarReaderView, required this.tabBarReaderView,
required this.tabBarShowContextualBar, required this.tabBarShowContextualBar,
required this.tabBarShowQuickTabSwitcherBar, required this.tabBarShowQuickTabSwitcherBar,
required this.tabBarPosition,
}); });
GeneralSettings.withDefaults({ GeneralSettings.withDefaults({
@@ -115,6 +119,7 @@ class GeneralSettings with FastEquatable {
bool? tabBarReaderView, bool? tabBarReaderView,
bool? tabBarShowContextualBar, bool? tabBarShowContextualBar,
bool? tabBarShowQuickTabSwitcherBar, bool? tabBarShowQuickTabSwitcherBar,
TabBarPosition? tabBarPosition,
}) : themeMode = themeMode ?? ThemeMode.dark, }) : themeMode = themeMode ?? ThemeMode.dark,
enableReadability = enableReadability ?? true, enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false, enforceReadability = enforceReadability ?? false,
@@ -134,7 +139,8 @@ class GeneralSettings with FastEquatable {
tabViewBottomSheet = tabViewBottomSheet ?? false, tabViewBottomSheet = tabViewBottomSheet ?? false,
tabBarReaderView = tabBarReaderView ?? false, tabBarReaderView = tabBarReaderView ?? false,
tabBarShowContextualBar = tabBarShowContextualBar ?? true, tabBarShowContextualBar = tabBarShowContextualBar ?? true,
tabBarShowQuickTabSwitcherBar = tabBarShowQuickTabSwitcherBar ?? true; tabBarShowQuickTabSwitcherBar = tabBarShowQuickTabSwitcherBar ?? true,
tabBarPosition = tabBarPosition ?? TabBarPosition.bottom;
factory GeneralSettings.fromJson(Map<String, dynamic> json) => factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
_$GeneralSettingsFromJson(json); _$GeneralSettingsFromJson(json);
@@ -161,5 +167,6 @@ class GeneralSettings with FastEquatable {
tabBarReaderView, tabBarReaderView,
tabBarShowContextualBar, tabBarShowContextualBar,
tabBarShowQuickTabSwitcherBar, tabBarShowQuickTabSwitcherBar,
tabBarPosition,
]; ];
} }
@@ -51,6 +51,8 @@ abstract class _$GeneralSettingsCWProxy {
bool tabBarShowQuickTabSwitcherBar, bool tabBarShowQuickTabSwitcherBar,
); );
GeneralSettings tabBarPosition(TabBarPosition tabBarPosition);
/// Creates a new instance with the provided field values. /// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`.
/// ///
@@ -77,6 +79,7 @@ abstract class _$GeneralSettingsCWProxy {
bool tabBarReaderView, bool tabBarReaderView,
bool tabBarShowContextualBar, bool tabBarShowContextualBar,
bool tabBarShowQuickTabSwitcherBar, bool tabBarShowQuickTabSwitcherBar,
TabBarPosition tabBarPosition,
}); });
} }
@@ -162,6 +165,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
bool tabBarShowQuickTabSwitcherBar, bool tabBarShowQuickTabSwitcherBar,
) => call(tabBarShowQuickTabSwitcherBar: tabBarShowQuickTabSwitcherBar); ) => call(tabBarShowQuickTabSwitcherBar: tabBarShowQuickTabSwitcherBar);
@override
GeneralSettings tabBarPosition(TabBarPosition tabBarPosition) =>
call(tabBarPosition: tabBarPosition);
@override @override
/// Creates a new instance with the provided field values. /// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`.
@@ -189,6 +196,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? tabBarReaderView = const $CopyWithPlaceholder(), Object? tabBarReaderView = const $CopyWithPlaceholder(),
Object? tabBarShowContextualBar = const $CopyWithPlaceholder(), Object? tabBarShowContextualBar = const $CopyWithPlaceholder(),
Object? tabBarShowQuickTabSwitcherBar = const $CopyWithPlaceholder(), Object? tabBarShowQuickTabSwitcherBar = const $CopyWithPlaceholder(),
Object? tabBarPosition = const $CopyWithPlaceholder(),
}) { }) {
return GeneralSettings( return GeneralSettings(
themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null
@@ -295,6 +303,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.tabBarShowQuickTabSwitcherBar ? _value.tabBarShowQuickTabSwitcherBar
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: tabBarShowQuickTabSwitcherBar as bool, : tabBarShowQuickTabSwitcherBar as bool,
tabBarPosition:
tabBarPosition == const $CopyWithPlaceholder() ||
tabBarPosition == null
? _value.tabBarPosition
// ignore: cast_nullable_to_non_nullable
: tabBarPosition as TabBarPosition,
); );
} }
} }
@@ -351,6 +365,10 @@ GeneralSettings _$GeneralSettingsFromJson(
tabBarReaderView: json['tabBarReaderView'] as bool?, tabBarReaderView: json['tabBarReaderView'] as bool?,
tabBarShowContextualBar: json['tabBarShowContextualBar'] as bool?, tabBarShowContextualBar: json['tabBarShowContextualBar'] as bool?,
tabBarShowQuickTabSwitcherBar: json['tabBarShowQuickTabSwitcherBar'] as bool?, tabBarShowQuickTabSwitcherBar: json['tabBarShowQuickTabSwitcherBar'] as bool?,
tabBarPosition: $enumDecodeNullable(
_$TabBarPositionEnumMap,
json['tabBarPosition'],
),
); );
Map<String, dynamic> _$GeneralSettingsToJson( Map<String, dynamic> _$GeneralSettingsToJson(
@@ -381,6 +399,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'tabBarReaderView': instance.tabBarReaderView, 'tabBarReaderView': instance.tabBarReaderView,
'tabBarShowContextualBar': instance.tabBarShowContextualBar, 'tabBarShowContextualBar': instance.tabBarShowContextualBar,
'tabBarShowQuickTabSwitcherBar': instance.tabBarShowQuickTabSwitcherBar, 'tabBarShowQuickTabSwitcherBar': instance.tabBarShowQuickTabSwitcherBar,
'tabBarPosition': _$TabBarPositionEnumMap[instance.tabBarPosition]!,
}; };
const _$ThemeModeEnumMap = { const _$ThemeModeEnumMap = {
@@ -422,3 +441,8 @@ const _$TabBarSwipeActionEnumMap = {
TabBarSwipeAction.switchLastOpened: 'switchLastOpened', TabBarSwipeAction.switchLastOpened: 'switchLastOpened',
TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs', TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs',
}; };
const _$TabBarPositionEnumMap = {
TabBarPosition.top: 'top',
TabBarPosition.bottom: 'bottom',
};
@@ -114,6 +114,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
), ),
'tabBarShowQuickTabSwitcherBar': settings['tabBarShowQuickTabSwitcherBar'] 'tabBarShowQuickTabSwitcherBar': settings['tabBarShowQuickTabSwitcherBar']
?.readAs(DriftSqlType.bool, db.typeMapping), ?.readAs(DriftSqlType.bool, db.typeMapping),
'tabBarPosition': settings['tabBarPosition']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
}); });
} }
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
} }
String _$generalSettingsRepositoryHash() => String _$generalSettingsRepositoryHash() =>
r'4014986c9510d6d7210c5c16d814f6b018a1f93c'; r'09542387844db25334384af115c538348b0ad33f';
abstract class _$GeneralSettingsRepository abstract class _$GeneralSettingsRepository
extends $StreamNotifier<GeneralSettings> { extends $StreamNotifier<GeneralSettings> {