diff --git a/app/lib/features/bangs/presentation/screens/user.dart b/app/lib/features/bangs/presentation/screens/user.dart index 48faabff..3efdd36a 100644 --- a/app/lib/features/bangs/presentation/screens/user.dart +++ b/app/lib/features/bangs/presentation/screens/user.dart @@ -29,6 +29,7 @@ import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/domain/repositories/data.dart'; import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class UserBangs extends HookConsumerWidget { static const _userGroupFilter = [BangGroup.user]; @@ -45,6 +46,9 @@ class UserBangs extends HookConsumerWidget { skipLoadingOnReload: true, data: (bangs) { return ListView.builder( + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: bangs.length, itemBuilder: (context, index) { final bang = bangs[index]; diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart index c7b701b7..3abe538d 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart @@ -33,6 +33,7 @@ import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; import 'package:weblibre/utils/ui_helper.dart'; class TabTreeDialog extends HookConsumerWidget { @@ -108,47 +109,54 @@ class TabTreeDialog extends HookConsumerWidget { extendBodyBehindAppBar: true, body: Skeletonizer( enabled: !tabs.hasValue || tabs.value?.isEmpty == true, - child: Skeleton.replace( - replacement: const Bone.square(), - child: GraphView.builder( - graph: graph, - algorithm: graphAlgo, - controller: graphViewController, - initialNode: selectedTabId.mapNotNull((tabId) => ValueKey(tabId)), - paint: Paint() - ..color = Theme.of(context).colorScheme.outline - ..strokeWidth = 1 - ..style = PaintingStyle.stroke, - builder: (Node node) { - final id = node.key!.value as String; - return SizedBox.fromSize( - size: childSize, - child: SingleGridTabPreview( - key: ValueKey(id), - tabId: id, - activeTabId: selectedTabId, - onClose: () { - final tabViewBottomSheet = ref - .read(generalSettingsWithDefaultsProvider) - .tabViewBottomSheet; + child: Padding( + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), + child: Skeleton.replace( + replacement: const Bone.square(), + child: GraphView.builder( + graph: graph, + algorithm: graphAlgo, + controller: graphViewController, + initialNode: selectedTabId.mapNotNull( + (tabId) => ValueKey(tabId), + ), + paint: Paint() + ..color = Theme.of(context).colorScheme.outline + ..strokeWidth = 1 + ..style = PaintingStyle.stroke, + builder: (Node node) { + final id = node.key!.value as String; + return SizedBox.fromSize( + size: childSize, + child: SingleGridTabPreview( + key: ValueKey(id), + tabId: id, + activeTabId: selectedTabId, + onClose: () { + final tabViewBottomSheet = ref + .read(generalSettingsWithDefaultsProvider) + .tabViewBottomSheet; - if (tabViewBottomSheet) { - ref - .read(bottomSheetControllerProvider.notifier) - .requestDismiss(); - } + if (tabViewBottomSheet) { + ref + .read(bottomSheetControllerProvider.notifier) + .requestDismiss(); + } - const BrowserRoute().go(context); - }, - onBeforeDelete: () { - if (graph.nodes.length <= 2) { - context.pop(); - } - }, - sourceSearchQuery: null, - ), - ); - }, + const BrowserRoute().go(context); + }, + onBeforeDelete: () { + if (graph.nodes.length <= 2) { + context.pop(); + } + }, + sourceSearchQuery: null, + ), + ); + }, + ), ), ), ), diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart b/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart index bfea791b..847a44aa 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart @@ -28,7 +28,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart'; import 'package:weblibre/features/sync/domain/repositories/sync.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; -import 'package:weblibre/presentation/hooks/scroll_visibility.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class TabViewScreen extends HookConsumerWidget { const TabViewScreen({super.key}); @@ -50,68 +50,67 @@ class TabViewScreen extends HookConsumerWidget { final scrollController = useScrollController(keys: [tabsReorderable]); - // Track FAB visibility based on scroll direction - final isFabVisible = useScrollVisibility(scrollController); - return Dialog.fullscreen( child: Scaffold( body: SafeArea( - child: switch (effectiveTabsViewMode) { - TabsViewMode.list => ViewTabListWidget( - key: ValueKey(tabsReorderable), - scrollController: scrollController, - tabsReorderable: tabsReorderable, - showNewTabFab: false, - onClose: () { - const BrowserRoute().go(context); - }, - ), - TabsViewMode.grid => ViewTabGridWidget( - key: ValueKey(tabsReorderable), - scrollController: scrollController, - tabsReorderable: tabsReorderable, - showNewTabFab: false, - onClose: () { - const BrowserRoute().go(context); - }, - ), - TabsViewMode.tree => ViewTabTreesWidget( - scrollController: scrollController, - showNewTabFab: false, - onClose: () { - const BrowserRoute().go(context); - }, - ), - }, + child: Builder( + builder: (safeAreaContext) { + return Padding( + padding: EdgeInsets.only( + bottom: isSyncedScope + ? 0 + : floatingActionButtonBottomInset(safeAreaContext), + ), + child: switch (effectiveTabsViewMode) { + TabsViewMode.list => ViewTabListWidget( + key: ValueKey(tabsReorderable), + scrollController: scrollController, + tabsReorderable: tabsReorderable, + showNewTabFab: false, + onClose: () { + const BrowserRoute().go(context); + }, + ), + TabsViewMode.grid => ViewTabGridWidget( + key: ValueKey(tabsReorderable), + scrollController: scrollController, + tabsReorderable: tabsReorderable, + showNewTabFab: false, + onClose: () { + const BrowserRoute().go(context); + }, + ), + TabsViewMode.tree => ViewTabTreesWidget( + scrollController: scrollController, + showNewTabFab: false, + onClose: () { + const BrowserRoute().go(context); + }, + ), + }, + ); + }, + ), ), floatingActionButton: isSyncedScope ? null - : AnimatedSlide( - duration: const Duration(milliseconds: 200), - offset: isFabVisible.value ? Offset.zero : const Offset(0, 2), - curve: Curves.easeInOut, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: isFabVisible.value ? 1.0 : 0.0, - child: FloatingActionButton( - onPressed: () async { - final settings = ref.read( - generalSettingsWithDefaultsProvider, - ); + : FloatingActionButton( + onPressed: () async { + final settings = ref.read( + generalSettingsWithDefaultsProvider, + ); - await SearchRoute( - tabType: - ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, - ).push(context); + await SearchRoute( + tabType: + ref.read(selectedTabTypeProvider) ?? + settings.defaultCreateTabType, + ).push(context); - if (context.mounted) { - const BrowserRoute().go(context); - } - }, - child: const Icon(Icons.add), - ), - ), + if (context.mounted) { + const BrowserRoute().go(context); + } + }, + child: const Icon(Icons.add), ), ), ); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart index 8ae05f4c..198badd9 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart @@ -47,6 +47,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/co import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class _TabDraggable extends HookConsumerWidget { final TabEntity entity; @@ -538,10 +539,20 @@ class ViewTabGridWidget extends HookConsumerWidget { ), ), ], - body: _TabGridView( - scrollController: scrollController, - tabsReorderable: tabsReorderable, - onClose: onClose, + body: Padding( + padding: EdgeInsets.only( + bottom: showNewTabFab + ? floatingActionButtonBottomInset( + context, + fabHeight: smallFloatingActionButtonHeight, + ) + : 0, + ), + child: _TabGridView( + scrollController: scrollController, + tabsReorderable: tabsReorderable, + onClose: onClose, + ), ), ), if (showNewTabFab) diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart index 662c877f..2071316a 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart @@ -46,6 +46,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/ta import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart'; import 'package:weblibre/features/sync/domain/repositories/sync.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class _TabDraggable extends HookConsumerWidget { final TabEntity entity; @@ -567,10 +568,20 @@ class ViewTabListWidget extends HookConsumerWidget { ), ), ], - body: _TabListView( - scrollController: scrollController, - tabsReorderable: tabsReorderable, - onClose: onClose, + body: Padding( + padding: EdgeInsets.only( + bottom: (showNewTabFab && !isSyncedScope) + ? floatingActionButtonBottomInset( + context, + fabHeight: smallFloatingActionButtonHeight, + ) + : 0, + ), + child: _TabListView( + scrollController: scrollController, + tabsReorderable: tabsReorderable, + onClose: onClose, + ), ), ), if (showNewTabFab && !isSyncedScope) diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart index 348eda94..b79c2f2b 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart @@ -38,6 +38,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selec import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; import 'package:weblibre/utils/ui_helper.dart' as ui_helper; class _TabTreePreview extends HookConsumerWidget { @@ -280,7 +281,14 @@ class ViewTabTreesWidget extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 4.0), child: GridView.builder( controller: scrollController, - padding: EdgeInsets.zero, + padding: EdgeInsets.only( + bottom: showNewTabFab + ? floatingActionButtonBottomInset( + context, + fabHeight: smallFloatingActionButtonHeight, + ) + : 0, + ), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( //Sync values for itemHeight calculation _calculateItemHeight childAspectRatio: 0.75, diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart index aa51b2d9..11f89425 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart @@ -36,6 +36,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/co import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart'; class ContainerDraftSuggestionsScreen extends HookConsumerWidget { @@ -55,140 +56,153 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget { return Scaffold( appBar: AppBar(title: const Text('Draft Containers')), body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: suggestionsAsync.when( - skipLoadingOnReload: true, - data: (suggestions) { - final screenWidth = MediaQuery.of(context).size.width; + child: Builder( + builder: (safeAreaContext) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: suggestionsAsync.when( + skipLoadingOnReload: true, + data: (suggestions) { + final screenWidth = MediaQuery.of(context).size.width; - final crossAxisCount = useMemoized(() { - final calculatedCount = calculateCrossAxisItemCount( - screenWidth: screenWidth, - horizontalPadding: 4.0, - crossAxisSpacing: 8.0, - ); + final crossAxisCount = useMemoized(() { + final calculatedCount = calculateCrossAxisItemCount( + screenWidth: screenWidth, + horizontalPadding: 4.0, + crossAxisSpacing: 8.0, + ); - return math.max( - math.min( - calculatedCount, - selectedContainer.value?.tabIds.length ?? 0, - ), - 2, - ); - }, [screenWidth, selectedContainer.value?.tabIds.length]); + return math.max( + math.min( + calculatedCount, + selectedContainer.value?.tabIds.length ?? 0, + ), + 2, + ); + }, [screenWidth, selectedContainer.value?.tabIds.length]); - return Column( - children: [ - SizedBox( - height: 48, - child: SelectableChips( - enableDelete: false, - sortSelectedFirst: false, - itemId: (container) => container, - itemAvatar: (container) => - const Icon(MdiIcons.creation, size: 20), - itemLabel: (container) => HookConsumer( - builder: (context, ref, child) { - final items = useListenableSelector( - selectedContainerTabs, - () => selectedContainerTabs.value[container], - ); - - final topic = items.isNotEmpty - ? ref.watch( - tabsTopicProvider(EquatableValue(items!)), - ) - : AsyncValue.data(container.topic); - - return topic.when( - skipLoadingOnReload: true, - data: (topic) => Text(topic ?? 'Untitled'), - error: (error, stackTrace) { - logger.e( - 'Failed predicting selected tabs topic', - error: error, - stackTrace: stackTrace, + return Column( + children: [ + SizedBox( + height: 48, + child: SelectableChips( + enableDelete: false, + sortSelectedFirst: false, + itemId: (container) => container, + itemAvatar: (container) => + const Icon(MdiIcons.creation, size: 20), + itemLabel: (container) => HookConsumer( + builder: (context, ref, child) { + final items = useListenableSelector( + selectedContainerTabs, + () => selectedContainerTabs.value[container], ); - return Text(container.topic ?? 'Untitled'); - }, - loading: () => - const Skeletonizer(child: Text('Untitled')), - ); - }, - ), - itemBadgeCount: (container) => container.tabIds.length, - availableItems: suggestions!, - selectedItem: selectedContainer.value, - onSelected: (item) { - selectedContainer.value = item; - }, - ), - ), - const Divider(), - const SizedBox(height: 4), - if (selectedContainer.value != null) - Expanded( - child: GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - //Sync values for itemHeight calculation _calculateItemHeight - childAspectRatio: 0.75, - mainAxisSpacing: 8.0, - crossAxisSpacing: 8.0, - crossAxisCount: crossAxisCount, - ), - itemCount: selectedContainer.value!.tabIds.length, - itemBuilder: (context, index) { - final tabId = selectedContainer.value!.tabIds[index]; - final equatable = selectedContainer.value!; + final topic = items.isNotEmpty + ? ref.watch( + tabsTopicProvider(EquatableValue(items!)), + ) + : AsyncValue.data(container.topic); - return (selectedContainerTabs.value[equatable] - ?.contains(tabId) == - true) - ? GridTabPreview( - tabId: tabId, - isActive: false, - onDelete: () { - selectedContainerTabs.value = { - ...selectedContainerTabs.value, - equatable: { - ...?selectedContainerTabs - .value[equatable], - }..remove(tabId), - }; - }, - ) - : SuggestedSingleGridTabPreview( - tabId: tabId, - activeTabId: null, - onTap: () { - selectedContainerTabs.value = { - ...selectedContainerTabs.value, - equatable: { - ...?selectedContainerTabs - .value[equatable], - tabId, - }, - }; - }, - ); - }, + return topic.when( + skipLoadingOnReload: true, + data: (topic) => Text(topic ?? 'Untitled'), + error: (error, stackTrace) { + logger.e( + 'Failed predicting selected tabs topic', + error: error, + stackTrace: stackTrace, + ); + + return Text(container.topic ?? 'Untitled'); + }, + loading: () => + const Skeletonizer(child: Text('Untitled')), + ); + }, + ), + itemBadgeCount: (container) => + container.tabIds.length, + availableItems: suggestions!, + selectedItem: selectedContainer.value, + onSelected: (item) { + selectedContainer.value = item; + }, + ), ), - ), - ], - ); - }, - error: (error, stackTrace) { - return FailureWidget( - title: 'Failed to create suggestions', - onRetry: () { - ref.invalidate(suggestClustersProvider); + const Divider(), + const SizedBox(height: 4), + if (selectedContainer.value != null) + Expanded( + child: GridView.builder( + padding: EdgeInsets.only( + bottom: (selectedTabs?.isNotEmpty ?? false) + ? floatingActionButtonBottomInset( + safeAreaContext, + ) + : 0, + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + //Sync values for itemHeight calculation _calculateItemHeight + childAspectRatio: 0.75, + mainAxisSpacing: 8.0, + crossAxisSpacing: 8.0, + crossAxisCount: crossAxisCount, + ), + itemCount: selectedContainer.value!.tabIds.length, + itemBuilder: (context, index) { + final tabId = + selectedContainer.value!.tabIds[index]; + final equatable = selectedContainer.value!; + + return (selectedContainerTabs.value[equatable] + ?.contains(tabId) == + true) + ? GridTabPreview( + tabId: tabId, + isActive: false, + onDelete: () { + selectedContainerTabs.value = { + ...selectedContainerTabs.value, + equatable: { + ...?selectedContainerTabs + .value[equatable], + }..remove(tabId), + }; + }, + ) + : SuggestedSingleGridTabPreview( + tabId: tabId, + activeTabId: null, + onTap: () { + selectedContainerTabs.value = { + ...selectedContainerTabs.value, + equatable: { + ...?selectedContainerTabs + .value[equatable], + tabId, + }, + }; + }, + ); + }, + ), + ), + ], + ); }, - ); - }, - loading: () => const Center(child: CircularProgressIndicator()), - ), + error: (error, stackTrace) { + return FailureWidget( + title: 'Failed to create suggestions', + onRetry: () { + ref.invalidate(suggestClustersProvider); + }, + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + ), + ); + }, ), ), floatingActionButton: (selectedTabs?.isNotEmpty ?? false) diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart index 63cb44f7..589dac85 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart @@ -33,6 +33,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart'; import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class ContainerListScreen extends HookConsumerWidget { const ContainerListScreen({super.key}); @@ -53,6 +54,9 @@ class ContainerListScreen extends HookConsumerWidget { builder: (context, controller) { return ListView.builder( controller: controller, + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: containers.length, itemBuilder: (context, index) { final container = containers[index]; @@ -164,6 +168,9 @@ class ContainerListScreen extends HookConsumerWidget { ), ), loading: () => ListView.builder( + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: 3, itemBuilder: (context, index) => ContainerListTile( ContainerData(id: 'null', color: Colors.transparent), diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart index 2aedb78e..bde4c003 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart @@ -31,6 +31,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/presentation/widgets/container_list_tile.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class ContainerSelectionScreen extends HookConsumerWidget { const ContainerSelectionScreen({super.key}); @@ -49,6 +50,9 @@ class ContainerSelectionScreen extends HookConsumerWidget { builder: (context, controller) { return ListView.builder( controller: controller, + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: containers.length, itemBuilder: (context, index) { final container = containers[index]; @@ -71,6 +75,9 @@ class ContainerSelectionScreen extends HookConsumerWidget { ), ), loading: () => ListView.builder( + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: 3, itemBuilder: (context, index) => ContainerListTile( ContainerData(id: Namespace.nil.value, color: Colors.transparent), diff --git a/app/lib/features/user/domain/presentation/screens/profile_list.dart b/app/lib/features/user/domain/presentation/screens/profile_list.dart index 75f97c8d..f7dfa8d1 100644 --- a/app/lib/features/user/domain/presentation/screens/profile_list.dart +++ b/app/lib/features/user/domain/presentation/screens/profile_list.dart @@ -26,6 +26,7 @@ import 'package:weblibre/core/filesystem.dart'; import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/features/user/domain/repositories/profile.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class ProfileListScreen extends HookConsumerWidget { const ProfileListScreen({super.key}); @@ -49,6 +50,9 @@ class ProfileListScreen extends HookConsumerWidget { body: usersAsync.when( skipLoadingOnReload: true, data: (profiles) => ListView.builder( + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: profiles.length, itemBuilder: (context, index) { final profile = profiles[index]; diff --git a/app/lib/features/web_feed/presentation/screens/feed_list.dart b/app/lib/features/web_feed/presentation/screens/feed_list.dart index f500f31f..15b21d5b 100644 --- a/app/lib/features/web_feed/presentation/screens/feed_list.dart +++ b/app/lib/features/web_feed/presentation/screens/feed_list.dart @@ -26,6 +26,7 @@ import 'package:weblibre/features/web_feed/domain/providers.dart'; import 'package:weblibre/features/web_feed/presentation/controllers/fetch_articles.dart'; import 'package:weblibre/features/web_feed/presentation/widgets/feed_card.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; +import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart'; class FeedListScreen extends HookConsumerWidget { const FeedListScreen({super.key}); @@ -76,6 +77,9 @@ class FeedListScreen extends HookConsumerWidget { .fetchAllArticles(); }, child: ListView.builder( + padding: EdgeInsets.only( + bottom: floatingActionButtonBottomInset(context), + ), itemCount: feeds.length, itemBuilder: (context, i) { return FeedCard(feed: feeds[i]); diff --git a/app/lib/presentation/widgets/floating_action_button_inset.dart b/app/lib/presentation/widgets/floating_action_button_inset.dart new file mode 100644 index 00000000..5c22236e --- /dev/null +++ b/app/lib/presentation/widgets/floating_action_button_inset.dart @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024-2026 Fabian Freund. + * + * This file is part of WebLibre + * (see https://weblibre.eu). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import 'package:flutter/material.dart'; + +const standardFloatingActionButtonHeight = kMinInteractiveDimension; +const smallFloatingActionButtonHeight = 40.0; + +double floatingActionButtonBottomInset( + BuildContext context, { + double fabHeight = standardFloatingActionButtonHeight, + double extraPadding = 8.0, +}) { + return fabHeight + + kFloatingActionButtonMargin + + MediaQuery.paddingOf(context).bottom + + extraPadding; +}