small web feature initial
This commit is contained in:
@@ -140,3 +140,23 @@ void _collectAllDescendantGuids(BookmarkFolder folder, Set<String> result) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns GUIDs of all bookmark entries matching [url] in the tree.
|
||||
List<String> bookmarkGuidsForUrl(BookmarkItem? root, Uri? url) {
|
||||
final result = <String>[];
|
||||
if (root == null || url == null) return result;
|
||||
|
||||
void collect(BookmarkItem item) {
|
||||
if (item is BookmarkEntry && item.url == url) {
|
||||
result.add(item.guid);
|
||||
}
|
||||
if (item is BookmarkFolder) {
|
||||
for (final child in item.children ?? const <BookmarkItem>[]) {
|
||||
collect(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collect(root);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ final class BrowserAddonServiceProvider
|
||||
}
|
||||
|
||||
String _$browserAddonServiceHash() =>
|
||||
r'1485fd056ce32e142e342e920affb82a5c77a370';
|
||||
r'6f07d93576a0816bf1b9e1b36397b89b7cacf43f';
|
||||
|
||||
abstract class _$BrowserAddonService extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
+25
-38
@@ -17,6 +17,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
@@ -30,8 +31,8 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/web_extensions_state.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/bookmarks/domain/entities/bookmark_item.dart';
|
||||
import 'package:weblibre/features/geckoview/features/bookmarks/domain/repositories/bookmarks.dart';
|
||||
import 'package:weblibre/features/geckoview/features/bookmarks/domain/utils/bookmark_tree_utils.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/font_size_constants.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart';
|
||||
@@ -689,11 +690,17 @@ class _BookmarkToolbarButton extends HookConsumerWidget {
|
||||
final tabUrl = scope.tabState?.url;
|
||||
final bookmarkable = tabUrl != null && !scope.isPreview;
|
||||
|
||||
final existingGuids = ref.watch(
|
||||
bookmarksRepositoryProvider.select(
|
||||
(async) => _bookmarkGuidsForUrl(async.value, tabUrl, bookmarkable),
|
||||
),
|
||||
);
|
||||
final existingGuids = ref
|
||||
.watch(
|
||||
bookmarksRepositoryProvider.select(
|
||||
(async) => EquatableValue(
|
||||
bookmarkable
|
||||
? bookmarkGuidsForUrl(async.value, tabUrl)
|
||||
: const <String>[],
|
||||
),
|
||||
),
|
||||
)
|
||||
.value;
|
||||
|
||||
final isBookmarked = existingGuids.isNotEmpty;
|
||||
|
||||
@@ -769,11 +776,18 @@ class _BookmarkToggleToolbarButton extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tabUrl = scope.tabState?.url;
|
||||
final bookmarkable = tabUrl != null && !scope.isPreview;
|
||||
final existingGuids = ref.watch(
|
||||
bookmarksRepositoryProvider.select(
|
||||
(async) => _bookmarkGuidsForUrl(async.value, tabUrl, bookmarkable),
|
||||
),
|
||||
);
|
||||
final existingGuids = ref
|
||||
.watch(
|
||||
bookmarksRepositoryProvider.select(
|
||||
(async) => EquatableValue(
|
||||
bookmarkable
|
||||
? bookmarkGuidsForUrl(async.value, tabUrl)
|
||||
: const <String>[],
|
||||
),
|
||||
),
|
||||
)
|
||||
.value;
|
||||
|
||||
final isBookmarked = existingGuids.isNotEmpty;
|
||||
|
||||
return IconButton(
|
||||
@@ -821,33 +835,6 @@ class _BookmarkToggleToolbarButton extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _bookmarkGuidsForUrl(
|
||||
BookmarkItem? root,
|
||||
Uri? tabUrl,
|
||||
bool bookmarkable,
|
||||
) {
|
||||
final result = <String>[];
|
||||
|
||||
if (!bookmarkable || root == null || tabUrl == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void collect(BookmarkItem item) {
|
||||
if (item is BookmarkEntry && item.url == tabUrl) {
|
||||
result.add(item.guid);
|
||||
}
|
||||
|
||||
if (item is BookmarkFolder) {
|
||||
for (final child in item.children ?? const <BookmarkItem>[]) {
|
||||
collect(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collect(root);
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> _adjustFontSize(
|
||||
BuildContext context,
|
||||
WidgetRef ref, {
|
||||
|
||||
@@ -56,6 +56,8 @@ import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_
|
||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/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/small_web/presentation/controllers/small_web_mode_controller.dart';
|
||||
import 'package:weblibre/features/small_web/presentation/widgets/small_web_browser_overlay.dart';
|
||||
import 'package:weblibre/features/sync/domain/repositories/sync.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
@@ -115,6 +117,8 @@ class _TabBar extends HookConsumerWidget {
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final Stream<Offset>? pointerMoveEvents;
|
||||
final TabBarPosition tabBarPosition;
|
||||
final bool isSmallWebMode;
|
||||
final bool enableGestures;
|
||||
|
||||
const _TabBar({
|
||||
required this.showMainToolbar,
|
||||
@@ -122,6 +126,8 @@ class _TabBar extends HookConsumerWidget {
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.tabBarPosition,
|
||||
required this.pointerMoveEvents,
|
||||
required this.isSmallWebMode,
|
||||
this.enableGestures = true,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -205,12 +211,15 @@ class _TabBar extends HookConsumerWidget {
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
isSmallWebMode: isSmallWebMode,
|
||||
enableGestures: enableGestures,
|
||||
),
|
||||
TabBarPosition.bottom => BrowserBottomAppBar(
|
||||
displayedSheet: displayedSheet,
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
isSmallWebMode: isSmallWebMode,
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -233,23 +242,33 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
|
||||
final overlayController = useOverlayPortalController();
|
||||
|
||||
final tabBarPosition = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarPosition,
|
||||
),
|
||||
final isSmallWebActive = ref.watch(
|
||||
smallWebModeControllerProvider.select((value) => value != null),
|
||||
);
|
||||
|
||||
final showContextualToolbar = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarShowContextualBar,
|
||||
),
|
||||
);
|
||||
final tabBarPosition = isSmallWebActive
|
||||
? TabBarPosition.top
|
||||
: ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarPosition,
|
||||
),
|
||||
);
|
||||
|
||||
final showQuickTabSwitcherBar = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarShowQuickTabSwitcherBar,
|
||||
),
|
||||
);
|
||||
final showContextualToolbar =
|
||||
!isSmallWebActive &&
|
||||
ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarShowContextualBar,
|
||||
),
|
||||
);
|
||||
|
||||
final showQuickTabSwitcherBar =
|
||||
!isSmallWebActive &&
|
||||
ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarShowQuickTabSwitcherBar,
|
||||
),
|
||||
);
|
||||
final quickTabSwitcherMode = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.effectiveUiQuickTabSwitcherMode(),
|
||||
@@ -261,11 +280,13 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
final displayQuickTabSwitcherBar =
|
||||
showQuickTabSwitcherBar && (quickTabSwitcherHasResults.value ?? false);
|
||||
|
||||
final autoHideTabBar = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.autoHideTabBar,
|
||||
),
|
||||
);
|
||||
final autoHideTabBar =
|
||||
!isSmallWebActive &&
|
||||
ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.autoHideTabBar,
|
||||
),
|
||||
);
|
||||
|
||||
ref.listen(overlayControllerProvider, (previous, next) {
|
||||
if (next != null) {
|
||||
@@ -338,13 +359,21 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
final bottomSafeArea = MediaQuery.of(context).padding.bottom;
|
||||
|
||||
// Calculate bottom toolbar size for FAB and sheet positioning
|
||||
// Pass actual displayedSheet to get correct height when ViewTabsSheet hides main toolbar
|
||||
final bottomAppBarContentSize = BrowserBottomAppBar(
|
||||
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
|
||||
displayedSheet: displayedSheet,
|
||||
).preferredSize;
|
||||
final Size bottomAppBarContentSize;
|
||||
if (isSmallWebActive) {
|
||||
bottomAppBarContentSize = const Size.fromHeight(
|
||||
SmallWebBrowserOverlay.barHeight,
|
||||
);
|
||||
} else {
|
||||
// Pass actual displayedSheet to get correct height when ViewTabsSheet hides main toolbar
|
||||
bottomAppBarContentSize = BrowserBottomAppBar(
|
||||
showMainToolbar: tabBarPosition == TabBarPosition.bottom,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
|
||||
isSmallWebMode: false,
|
||||
displayedSheet: displayedSheet,
|
||||
).preferredSize;
|
||||
}
|
||||
// Total height includes safe area padding
|
||||
final bottomAppBarTotalHeight =
|
||||
bottomAppBarContentSize.height + bottomSafeArea;
|
||||
@@ -355,6 +384,8 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
showMainToolbar: tabBarPosition == TabBarPosition.top,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
|
||||
isSmallWebMode: isSmallWebActive,
|
||||
enableGestures: !isSmallWebActive,
|
||||
).preferredSize;
|
||||
final topAppBarTotalHeight = topAppBarContentSize.height + topSafeArea;
|
||||
|
||||
@@ -623,36 +654,49 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
),
|
||||
|
||||
// Layer 2: Bottom Toolbar (overlay, slides in/out)
|
||||
// In small web mode, show the discovery overlay instead
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final toolbarState = ref.watch(
|
||||
toolbarVisibilityControllerProvider(selectedTabId),
|
||||
);
|
||||
final visible =
|
||||
sheetDisplayed ||
|
||||
(!tabInFullScreen &&
|
||||
toolbarState == ToolbarVisibility.visible);
|
||||
return _AnimatedToolbar(
|
||||
position: TabBarPosition.bottom,
|
||||
visible: visible,
|
||||
child: _TabBar(
|
||||
tabBarPosition: TabBarPosition.bottom,
|
||||
showMainToolbar:
|
||||
tabBarPosition == TabBarPosition.bottom,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
|
||||
pointerMoveEvents:
|
||||
tabBarPosition == TabBarPosition.bottom
|
||||
? pointerMoveEventsController.stream
|
||||
: null,
|
||||
child: isSmallWebActive
|
||||
? _AnimatedToolbar(
|
||||
position: TabBarPosition.bottom,
|
||||
visible: !tabInFullScreen,
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
elevation: 3,
|
||||
child: const SmallWebBrowserOverlay(),
|
||||
),
|
||||
)
|
||||
: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final toolbarState = ref.watch(
|
||||
toolbarVisibilityControllerProvider(selectedTabId),
|
||||
);
|
||||
final visible =
|
||||
sheetDisplayed ||
|
||||
(!tabInFullScreen &&
|
||||
toolbarState == ToolbarVisibility.visible);
|
||||
return _AnimatedToolbar(
|
||||
position: TabBarPosition.bottom,
|
||||
visible: visible,
|
||||
child: _TabBar(
|
||||
tabBarPosition: TabBarPosition.bottom,
|
||||
showMainToolbar:
|
||||
tabBarPosition == TabBarPosition.bottom,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar:
|
||||
displayQuickTabSwitcherBar,
|
||||
isSmallWebMode: false,
|
||||
pointerMoveEvents:
|
||||
tabBarPosition == TabBarPosition.bottom
|
||||
? pointerMoveEventsController.stream
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Layer 3: Top Toolbar (overlay, slides in/out) - only when position is top
|
||||
@@ -678,7 +722,11 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
showMainToolbar: true,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: displayQuickTabSwitcherBar,
|
||||
pointerMoveEvents: pointerMoveEventsController.stream,
|
||||
isSmallWebMode: isSmallWebActive,
|
||||
enableGestures: !isSmallWebActive,
|
||||
pointerMoveEvents: isSmallWebActive
|
||||
? null
|
||||
: pointerMoveEventsController.stream,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+14
-1
@@ -66,6 +66,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/top_sites/domain/repositories/top_site_repository.dart';
|
||||
import 'package:weblibre/features/small_web/presentation/controllers/small_web_mode_controller.dart';
|
||||
import 'package:weblibre/features/sync/domain/entities/sync_repository_state.dart';
|
||||
import 'package:weblibre/features/sync/domain/repositories/sync.dart';
|
||||
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
|
||||
@@ -1918,7 +1919,19 @@ class _QuickLinksGrid extends ConsumerWidget {
|
||||
}),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(child: SizedBox.shrink()),
|
||||
Expanded(
|
||||
child: _buildGridItem(
|
||||
context,
|
||||
Icons.explore,
|
||||
'Small Web',
|
||||
() async {
|
||||
Navigator.pop(context);
|
||||
await ref
|
||||
.read(smallWebModeControllerProvider.notifier)
|
||||
.enter();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(child: SizedBox.shrink()),
|
||||
],
|
||||
|
||||
+101
-53
@@ -40,6 +40,9 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/contro
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/toolbar_button.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/tabs/data/entities/tab_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
|
||||
@@ -52,6 +55,8 @@ class BrowserTopAppBar extends StatelessWidget {
|
||||
final bool showMainToolbar;
|
||||
final bool showContextualToolbar;
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final bool isSmallWebMode;
|
||||
final bool enableGestures;
|
||||
|
||||
late final BrowserTabBar _tabBar;
|
||||
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
||||
@@ -61,12 +66,16 @@ class BrowserTopAppBar extends StatelessWidget {
|
||||
required this.showMainToolbar,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.isSmallWebMode,
|
||||
this.enableGestures = true,
|
||||
}) {
|
||||
_tabBar = BrowserTabBar(
|
||||
showMainToolbar: showMainToolbar,
|
||||
displayedSheet: null,
|
||||
showContextualToolbar: false,
|
||||
showQuickTabSwitcherBar: false,
|
||||
isSmallWebMode: isSmallWebMode,
|
||||
enableGestures: enableGestures,
|
||||
hideMainToolbarButtonsDuplicatedInContextualToolbar:
|
||||
showContextualToolbar,
|
||||
);
|
||||
@@ -86,7 +95,9 @@ class BrowserBottomAppBar extends StatelessWidget {
|
||||
final bool showMainToolbar;
|
||||
final bool showContextualToolbar;
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final bool isSmallWebMode;
|
||||
final Sheet? displayedSheet;
|
||||
final bool enableGestures;
|
||||
|
||||
late final BrowserTabBar _tabBar;
|
||||
late final _size = Size.fromHeight(_tabBar.getToolbarHeight());
|
||||
@@ -97,12 +108,16 @@ class BrowserBottomAppBar extends StatelessWidget {
|
||||
required this.displayedSheet,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.isSmallWebMode,
|
||||
this.enableGestures = true,
|
||||
}) {
|
||||
_tabBar = BrowserTabBar(
|
||||
displayedSheet: displayedSheet,
|
||||
showMainToolbar: showMainToolbar,
|
||||
showContextualToolbar: showContextualToolbar,
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
isSmallWebMode: isSmallWebMode,
|
||||
enableGestures: enableGestures,
|
||||
hideMainToolbarButtonsDuplicatedInContextualToolbar:
|
||||
showContextualToolbar,
|
||||
);
|
||||
@@ -133,6 +148,8 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
final bool showQuickTabSwitcherBar;
|
||||
final Sheet? displayedSheet;
|
||||
final bool hideMainToolbarButtonsDuplicatedInContextualToolbar;
|
||||
final bool isSmallWebMode;
|
||||
final bool enableGestures;
|
||||
|
||||
const BrowserTabBar({
|
||||
super.key,
|
||||
@@ -140,6 +157,8 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
required this.displayedSheet,
|
||||
required this.showContextualToolbar,
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.isSmallWebMode,
|
||||
required this.enableGestures,
|
||||
this.hideMainToolbarButtonsDuplicatedInContextualToolbar = false,
|
||||
});
|
||||
|
||||
@@ -195,8 +214,9 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
c.buttonId == ToolbarButtonId.navigationMenu.name && c.isVisible,
|
||||
);
|
||||
|
||||
final showMainToolbarTabsCount = !tabsCountInContextual;
|
||||
final showMainToolbarNavigationButton = !menuInContextual;
|
||||
final showMainToolbarTabsCount = !isSmallWebMode && !tabsCountInContextual;
|
||||
final showMainToolbarNavigationButton =
|
||||
!isSmallWebMode && !menuInContextual;
|
||||
|
||||
final containerColor = ref.watch(
|
||||
watchTabContainerDataProvider(
|
||||
@@ -232,6 +252,19 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
: const AppBarTitle()
|
||||
: null,
|
||||
actions: [
|
||||
if (isSmallWebMode)
|
||||
ReaderButton(
|
||||
buttonBuilder: (isLoading, readerActive, icon) => ToolbarButton(
|
||||
onTap: isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(!readerActive);
|
||||
},
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
if (showMainToolbarTabsCount)
|
||||
TabsCountButton(
|
||||
selectedTabId: selectedTabId,
|
||||
@@ -248,61 +281,76 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
selectedTabId: selectedTabId,
|
||||
displayedSheet: displayedSheet,
|
||||
),
|
||||
onHorizontalDragStart: (details) {
|
||||
dragStartPosition.value = details.globalPosition;
|
||||
},
|
||||
onHorizontalDragEnd: (details) async {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
onHorizontalDragStart: !enableGestures
|
||||
? null
|
||||
: (details) {
|
||||
dragStartPosition.value = details.globalPosition;
|
||||
},
|
||||
onHorizontalDragEnd: !enableGestures
|
||||
? null
|
||||
: (details) async {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
|
||||
if (distance.dx.abs() > 50 && distance.dy.abs() < 20) {
|
||||
final selectedTab = ref.read(selectedTabProvider);
|
||||
final setting = await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetchSettings();
|
||||
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)
|
||||
.selectPreviousTab(selectedTab);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectNextTab(selectedTab);
|
||||
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)
|
||||
.selectPreviousTab(selectedTab);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.selectNextTab(selectedTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onVerticalDragStart: (details) {
|
||||
dragStartPosition.value = details.globalPosition;
|
||||
},
|
||||
onVerticalDragEnd: (details) {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
}
|
||||
},
|
||||
onVerticalDragStart: !enableGestures
|
||||
? null
|
||||
: (details) {
|
||||
dragStartPosition.value = details.globalPosition;
|
||||
},
|
||||
onVerticalDragEnd: !enableGestures
|
||||
? null
|
||||
: (details) {
|
||||
final distance = dragStartPosition.value - details.globalPosition;
|
||||
|
||||
// Swipe direction for dismiss depends on toolbar position:
|
||||
// - Bottom bar: swipe down to dismiss (positive distance.dy)
|
||||
// - Top bar: swipe up to dismiss (negative distance.dy)
|
||||
const dismissThreshold = kToolbarHeight * 0.5;
|
||||
final shouldDismiss = switch (tabBarPosition) {
|
||||
TabBarPosition.bottom =>
|
||||
distance.dy.isNegative && distance.dy.abs() > dismissThreshold,
|
||||
TabBarPosition.top =>
|
||||
!distance.dy.isNegative && distance.dy.abs() > dismissThreshold,
|
||||
};
|
||||
if (shouldDismiss && ref.read(bottomSheetControllerProvider) == null) {
|
||||
unawaited(HapticFeedback.lightImpact());
|
||||
ref
|
||||
.read(toolbarVisibilityControllerProvider(selectedTabId).notifier)
|
||||
.dismiss();
|
||||
}
|
||||
},
|
||||
// Swipe direction for dismiss depends on toolbar position:
|
||||
// - Bottom bar: swipe down to dismiss (positive distance.dy)
|
||||
// - Top bar: swipe up to dismiss (negative distance.dy)
|
||||
const dismissThreshold = kToolbarHeight * 0.5;
|
||||
final shouldDismiss = switch (tabBarPosition) {
|
||||
TabBarPosition.bottom =>
|
||||
distance.dy.isNegative &&
|
||||
distance.dy.abs() > dismissThreshold,
|
||||
TabBarPosition.top =>
|
||||
!distance.dy.isNegative &&
|
||||
distance.dy.abs() > dismissThreshold,
|
||||
};
|
||||
if (shouldDismiss &&
|
||||
ref.read(bottomSheetControllerProvider) == null) {
|
||||
unawaited(HapticFeedback.lightImpact());
|
||||
ref
|
||||
.read(
|
||||
toolbarVisibilityControllerProvider(
|
||||
selectedTabId,
|
||||
).notifier,
|
||||
)
|
||||
.dismiss();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -159,11 +159,13 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
data: (androidInfo) {
|
||||
if (androidInfo == null) {
|
||||
// Not Android, always show GeckoView based on route
|
||||
return topRoute is GoRoute && topRoute.name == BrowserRoute.name;
|
||||
return topRoute is GoRoute &&
|
||||
topRoute.name == BrowserRoute.name;
|
||||
}
|
||||
// Android: only apply visibility fix on Android 12 and lower (API <= 31)
|
||||
if (androidInfo.sdkInt <= 31) {
|
||||
return topRoute is GoRoute && topRoute.name == BrowserRoute.name;
|
||||
return topRoute is GoRoute &&
|
||||
topRoute.name == BrowserRoute.name;
|
||||
}
|
||||
// Android 13+: always show GeckoView
|
||||
return true;
|
||||
|
||||
+5
-74
@@ -32,6 +32,7 @@ import 'package:weblibre/features/geckoview/features/browser/domain/providers.da
|
||||
import 'package:weblibre/features/geckoview/features/search/presentation/dialogs/reset_bang_dialog.dart';
|
||||
import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart';
|
||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||
import 'package:weblibre/presentation/widgets/sliding_pill_toggle.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
@@ -172,86 +173,16 @@ class _TabbedBangSelector extends HookConsumerWidget {
|
||||
return () => tabController.removeListener(listener);
|
||||
}, [tabController]);
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final disableAnimations = MediaQuery.disableAnimationsOf(context);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Animated sliding pill toggle
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: Container(
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedAlign(
|
||||
alignment: tabIndex.value == 1
|
||||
? Alignment.centerRight
|
||||
: Alignment.centerLeft,
|
||||
duration: disableAnimations
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 0.5,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => tabController.animateTo(0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Search On This Site',
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: tabIndex.value == 0
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
fontWeight: tabIndex.value == 0
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => tabController.animateTo(1),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'All Providers',
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: tabIndex.value == 1
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
fontWeight: tabIndex.value == 1
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SlidingPillToggle(
|
||||
selectedIndex: tabIndex.value,
|
||||
labels: const ['Search On This Site', 'All Providers'],
|
||||
onChanged: (index) => tabController.animateTo(index),
|
||||
),
|
||||
),
|
||||
// Tab content
|
||||
|
||||
@@ -48,4 +48,4 @@ final class TabDatabaseProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabDatabaseHash() => r'c40af8d5e17f61abcf14ea096ace3d0c2fb3d8d8';
|
||||
String _$tabDatabaseHash() => r'62466f063f5eae2a32c37dce0496b8e47e7cb953';
|
||||
|
||||
@@ -49,4 +49,4 @@ final class TopSiteDatabaseProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$topSiteDatabaseHash() => r'bee041ef1b144ce63c38080b2534e4109399fa6f';
|
||||
String _$topSiteDatabaseHash() => r'a5afe9a807174ae3382931612b25f959efccdded';
|
||||
|
||||
Reference in New Issue
Block a user