small web feature initial

This commit is contained in:
Fabian Freund
2026-03-23 11:13:39 +01:00
parent 1e56bb3e3a
commit b9669635d9
61 changed files with 10455 additions and 233 deletions
@@ -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();
@@ -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,
),
);
},
@@ -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()),
],
@@ -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();
}
},
);
}
}
@@ -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;
@@ -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';