Revert "add fab insets avoid overlapping"

This reverts commit 5230d3cb6b.
This commit is contained in:
Fabian Freund
2026-02-27 20:53:30 +01:00
parent 51906ef61f
commit f99484b0fa
12 changed files with 229 additions and 340 deletions
@@ -29,7 +29,6 @@ import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart'; import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart'; import 'package:weblibre/features/bangs/presentation/widgets/bang_details.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
class UserBangs extends HookConsumerWidget { class UserBangs extends HookConsumerWidget {
static const _userGroupFilter = [BangGroup.user]; static const _userGroupFilter = [BangGroup.user];
@@ -46,9 +45,6 @@ class UserBangs extends HookConsumerWidget {
skipLoadingOnReload: true, skipLoadingOnReload: true,
data: (bangs) { data: (bangs) {
return ListView.builder( return ListView.builder(
padding: EdgeInsets.only(
bottom: floatingActionButtonBottomInset(context),
),
itemCount: bangs.length, itemCount: bangs.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final bang = bangs[index]; final bang = bangs[index];
@@ -33,7 +33,6 @@ 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/browser/presentation/widgets/tab_view/tab_preview.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/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/widgets/floating_action_button_inset.dart';
import 'package:weblibre/utils/ui_helper.dart'; import 'package:weblibre/utils/ui_helper.dart';
class TabTreeDialog extends HookConsumerWidget { class TabTreeDialog extends HookConsumerWidget {
@@ -109,54 +108,47 @@ class TabTreeDialog extends HookConsumerWidget {
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
body: Skeletonizer( body: Skeletonizer(
enabled: !tabs.hasValue || tabs.value?.isEmpty == true, enabled: !tabs.hasValue || tabs.value?.isEmpty == true,
child: Padding( child: Skeleton.replace(
padding: EdgeInsets.only( replacement: const Bone.square(),
bottom: floatingActionButtonBottomInset(context), child: GraphView.builder(
), graph: graph,
child: Skeleton.replace( algorithm: graphAlgo,
replacement: const Bone.square(), controller: graphViewController,
child: GraphView.builder( initialNode: selectedTabId.mapNotNull((tabId) => ValueKey(tabId)),
graph: graph, paint: Paint()
algorithm: graphAlgo, ..color = Theme.of(context).colorScheme.outline
controller: graphViewController, ..strokeWidth = 1
initialNode: selectedTabId.mapNotNull( ..style = PaintingStyle.stroke,
(tabId) => ValueKey(tabId), builder: (Node node) {
), final id = node.key!.value as String;
paint: Paint() return SizedBox.fromSize(
..color = Theme.of(context).colorScheme.outline size: childSize,
..strokeWidth = 1 child: SingleGridTabPreview(
..style = PaintingStyle.stroke, key: ValueKey(id),
builder: (Node node) { tabId: id,
final id = node.key!.value as String; activeTabId: selectedTabId,
return SizedBox.fromSize( onClose: () {
size: childSize, final tabViewBottomSheet = ref
child: SingleGridTabPreview( .read(generalSettingsWithDefaultsProvider)
key: ValueKey(id), .tabViewBottomSheet;
tabId: id,
activeTabId: selectedTabId,
onClose: () {
final tabViewBottomSheet = ref
.read(generalSettingsWithDefaultsProvider)
.tabViewBottomSheet;
if (tabViewBottomSheet) { if (tabViewBottomSheet) {
ref ref
.read(bottomSheetControllerProvider.notifier) .read(bottomSheetControllerProvider.notifier)
.requestDismiss(); .requestDismiss();
} }
const BrowserRoute().go(context); const BrowserRoute().go(context);
}, },
onBeforeDelete: () { onBeforeDelete: () {
if (graph.nodes.length <= 2) { if (graph.nodes.length <= 2) {
context.pop(); context.pop();
} }
}, },
sourceSearchQuery: null, sourceSearchQuery: null,
), ),
); );
}, },
),
), ),
), ),
), ),
@@ -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/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart';
import 'package:weblibre/features/sync/domain/repositories/sync.dart'; import 'package:weblibre/features/sync/domain/repositories/sync.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/widgets/floating_action_button_inset.dart'; import 'package:weblibre/presentation/hooks/scroll_visibility.dart';
class TabViewScreen extends HookConsumerWidget { class TabViewScreen extends HookConsumerWidget {
const TabViewScreen({super.key}); const TabViewScreen({super.key});
@@ -50,67 +50,68 @@ class TabViewScreen extends HookConsumerWidget {
final scrollController = useScrollController(keys: [tabsReorderable]); final scrollController = useScrollController(keys: [tabsReorderable]);
// Track FAB visibility based on scroll direction
final isFabVisible = useScrollVisibility(scrollController);
return Dialog.fullscreen( return Dialog.fullscreen(
child: Scaffold( child: Scaffold(
body: SafeArea( body: SafeArea(
child: Builder( child: switch (effectiveTabsViewMode) {
builder: (safeAreaContext) { TabsViewMode.list => ViewTabListWidget(
return Padding( key: ValueKey(tabsReorderable),
padding: EdgeInsets.only( scrollController: scrollController,
bottom: isSyncedScope tabsReorderable: tabsReorderable,
? 0 showNewTabFab: false,
: floatingActionButtonBottomInset(safeAreaContext), onClose: () {
), const BrowserRoute().go(context);
child: switch (effectiveTabsViewMode) { },
TabsViewMode.list => ViewTabListWidget( ),
key: ValueKey(tabsReorderable), TabsViewMode.grid => ViewTabGridWidget(
scrollController: scrollController, key: ValueKey(tabsReorderable),
tabsReorderable: tabsReorderable, scrollController: scrollController,
showNewTabFab: false, tabsReorderable: tabsReorderable,
onClose: () { showNewTabFab: false,
const BrowserRoute().go(context); onClose: () {
}, const BrowserRoute().go(context);
), },
TabsViewMode.grid => ViewTabGridWidget( ),
key: ValueKey(tabsReorderable), TabsViewMode.tree => ViewTabTreesWidget(
scrollController: scrollController, scrollController: scrollController,
tabsReorderable: tabsReorderable, showNewTabFab: false,
showNewTabFab: false, onClose: () {
onClose: () { const BrowserRoute().go(context);
const BrowserRoute().go(context); },
}, ),
), },
TabsViewMode.tree => ViewTabTreesWidget(
scrollController: scrollController,
showNewTabFab: false,
onClose: () {
const BrowserRoute().go(context);
},
),
},
);
},
),
), ),
floatingActionButton: isSyncedScope floatingActionButton: isSyncedScope
? null ? null
: FloatingActionButton( : AnimatedSlide(
onPressed: () async { duration: const Duration(milliseconds: 200),
final settings = ref.read( offset: isFabVisible.value ? Offset.zero : const Offset(0, 2),
generalSettingsWithDefaultsProvider, 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,
);
await SearchRoute( await SearchRoute(
tabType: tabType:
ref.read(selectedTabTypeProvider) ?? ref.read(selectedTabTypeProvider) ??
settings.effectiveDefaultCreateTabType, settings.effectiveDefaultCreateTabType,
).push(context); ).push(context);
if (context.mounted) { if (context.mounted) {
const BrowserRoute().go(context); const BrowserRoute().go(context);
} }
}, },
child: const Icon(Icons.add), child: const Icon(Icons.add),
),
),
), ),
), ),
); );
@@ -47,7 +47,6 @@ 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.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.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/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
class _TabDraggable extends HookConsumerWidget { class _TabDraggable extends HookConsumerWidget {
final TabEntity entity; final TabEntity entity;
@@ -539,20 +538,10 @@ class ViewTabGridWidget extends HookConsumerWidget {
), ),
), ),
], ],
body: Padding( body: _TabGridView(
padding: EdgeInsets.only( scrollController: scrollController,
bottom: showNewTabFab tabsReorderable: tabsReorderable,
? floatingActionButtonBottomInset( onClose: onClose,
context,
fabHeight: smallFloatingActionButtonHeight,
)
: 0,
),
child: _TabGridView(
scrollController: scrollController,
tabsReorderable: tabsReorderable,
onClose: onClose,
),
), ),
), ),
if (showNewTabFab) if (showNewTabFab)
@@ -46,7 +46,6 @@ 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/geckoview/features/tabs/domain/repositories/tab_search.dart';
import 'package:weblibre/features/sync/domain/repositories/sync.dart'; import 'package:weblibre/features/sync/domain/repositories/sync.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/widgets/floating_action_button_inset.dart';
class _TabDraggable extends HookConsumerWidget { class _TabDraggable extends HookConsumerWidget {
final TabEntity entity; final TabEntity entity;
@@ -568,20 +567,10 @@ class ViewTabListWidget extends HookConsumerWidget {
), ),
), ),
], ],
body: Padding( body: _TabListView(
padding: EdgeInsets.only( scrollController: scrollController,
bottom: (showNewTabFab && !isSyncedScope) tabsReorderable: tabsReorderable,
? floatingActionButtonBottomInset( onClose: onClose,
context,
fabHeight: smallFloatingActionButtonHeight,
)
: 0,
),
child: _TabListView(
scrollController: scrollController,
tabsReorderable: tabsReorderable,
onClose: onClose,
),
), ),
), ),
if (showNewTabFab && !isSyncedScope) if (showNewTabFab && !isSyncedScope)
@@ -38,7 +38,6 @@ 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.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.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/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; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
class _TabTreePreview extends HookConsumerWidget { class _TabTreePreview extends HookConsumerWidget {
@@ -308,14 +307,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: GridView.builder( child: GridView.builder(
controller: scrollController, controller: scrollController,
padding: EdgeInsets.only( padding: EdgeInsets.zero,
bottom: showNewTabFab
? floatingActionButtonBottomInset(
context,
fabHeight: smallFloatingActionButtonHeight,
)
: 0,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//Sync values for itemHeight calculation _calculateItemHeight //Sync values for itemHeight calculation _calculateItemHeight
childAspectRatio: 0.75, childAspectRatio: 0.75,
@@ -36,7 +36,6 @@ 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/gecko_inference.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.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/failure_widget.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart';
class ContainerDraftSuggestionsScreen extends HookConsumerWidget { class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
@@ -56,153 +55,140 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Draft Containers')), appBar: AppBar(title: const Text('Draft Containers')),
body: SafeArea( body: SafeArea(
child: Builder( child: Padding(
builder: (safeAreaContext) { padding: const EdgeInsets.symmetric(horizontal: 8.0),
return Padding( child: suggestionsAsync.when(
padding: const EdgeInsets.symmetric(horizontal: 8.0), skipLoadingOnReload: true,
child: suggestionsAsync.when( data: (suggestions) {
skipLoadingOnReload: true, final screenWidth = MediaQuery.of(context).size.width;
data: (suggestions) {
final screenWidth = MediaQuery.of(context).size.width;
final crossAxisCount = useMemoized(() { final crossAxisCount = useMemoized(() {
final calculatedCount = calculateCrossAxisItemCount( final calculatedCount = calculateCrossAxisItemCount(
screenWidth: screenWidth, screenWidth: screenWidth,
horizontalPadding: 4.0, horizontalPadding: 4.0,
crossAxisSpacing: 8.0, crossAxisSpacing: 8.0,
); );
return math.max( return math.max(
math.min( math.min(
calculatedCount, calculatedCount,
selectedContainer.value?.tabIds.length ?? 0, selectedContainer.value?.tabIds.length ?? 0,
), ),
2, 2,
); );
}, [screenWidth, selectedContainer.value?.tabIds.length]); }, [screenWidth, selectedContainer.value?.tabIds.length]);
return Column( return Column(
children: [ children: [
SizedBox( SizedBox(
height: 48, height: 48,
child: SelectableChips( child: SelectableChips(
enableDelete: false, enableDelete: false,
sortSelectedFirst: false, sortSelectedFirst: false,
itemId: (container) => container, itemId: (container) => container,
itemAvatar: (container) => itemAvatar: (container) =>
const Icon(MdiIcons.creation, size: 20), const Icon(MdiIcons.creation, size: 20),
itemLabel: (container) => HookConsumer( itemLabel: (container) => HookConsumer(
builder: (context, ref, child) { builder: (context, ref, child) {
final items = useListenableSelector( final items = useListenableSelector(
selectedContainerTabs, selectedContainerTabs,
() => selectedContainerTabs.value[container], () => 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,
); );
final topic = items.isNotEmpty return Text(container.topic ?? 'Untitled');
? 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 Text(container.topic ?? 'Untitled');
},
loading: () =>
const Skeletonizer(child: Text('Untitled')),
);
}, },
), loading: () =>
itemBadgeCount: (container) => const Skeletonizer(child: Text('Untitled')),
container.tabIds.length, );
availableItems: suggestions!, },
selectedItem: selectedContainer.value,
onSelected: (item) {
selectedContainer.value = item;
},
),
), ),
const Divider(), itemBadgeCount: (container) => container.tabIds.length,
const SizedBox(height: 4), availableItems: suggestions!,
if (selectedContainer.value != null) selectedItem: selectedContainer.value,
Expanded( onSelected: (item) {
child: GridView.builder( selectedContainer.value = item;
padding: EdgeInsets.only( },
bottom: (selectedTabs?.isNotEmpty ?? false) ),
? floatingActionButtonBottomInset( ),
safeAreaContext, const Divider(),
) const SizedBox(height: 4),
: 0, if (selectedContainer.value != null)
), Expanded(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( child: GridView.builder(
//Sync values for itemHeight calculation _calculateItemHeight gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.75, //Sync values for itemHeight calculation _calculateItemHeight
mainAxisSpacing: 8.0, childAspectRatio: 0.75,
crossAxisSpacing: 8.0, mainAxisSpacing: 8.0,
crossAxisCount: crossAxisCount, 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,
},
};
},
);
},
),
), ),
], 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,
},
};
},
);
},
),
),
],
);
},
error: (error, stackTrace) {
return FailureWidget(
title: 'Failed to create suggestions',
onRetry: () {
ref.invalidate(suggestClustersProvider);
}, },
error: (error, stackTrace) { );
return FailureWidget( },
title: 'Failed to create suggestions', loading: () => const Center(child: CircularProgressIndicator()),
onRetry: () { ),
ref.invalidate(suggestClustersProvider);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
),
);
},
), ),
), ),
floatingActionButton: (selectedTabs?.isNotEmpty ?? false) floatingActionButton: (selectedTabs?.isNotEmpty ?? false)
@@ -33,7 +33,6 @@ 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/controllers/start_tor_proxy.dart';
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart'; import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
class ContainerListScreen extends HookConsumerWidget { class ContainerListScreen extends HookConsumerWidget {
const ContainerListScreen({super.key}); const ContainerListScreen({super.key});
@@ -54,9 +53,6 @@ class ContainerListScreen extends HookConsumerWidget {
builder: (context, controller) { builder: (context, controller) {
return ListView.builder( return ListView.builder(
controller: controller, controller: controller,
padding: EdgeInsets.only(
bottom: floatingActionButtonBottomInset(context),
),
itemCount: containers.length, itemCount: containers.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final container = containers[index]; final container = containers[index];
@@ -168,9 +164,6 @@ class ContainerListScreen extends HookConsumerWidget {
), ),
), ),
loading: () => ListView.builder( loading: () => ListView.builder(
padding: EdgeInsets.only(
bottom: floatingActionButtonBottomInset(context),
),
itemCount: 3, itemCount: 3,
itemBuilder: (context, index) => ContainerListTile( itemBuilder: (context, index) => ContainerListTile(
ContainerData(id: 'null', color: Colors.transparent), ContainerData(id: 'null', color: Colors.transparent),
@@ -34,7 +34,6 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selec
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.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/features/geckoview/features/tabs/presentation/widgets/container_list_tile.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
class ContainerSelectionScreen extends HookConsumerWidget { class ContainerSelectionScreen extends HookConsumerWidget {
const ContainerSelectionScreen({super.key}); const ContainerSelectionScreen({super.key});
@@ -55,10 +54,7 @@ class ContainerSelectionScreen extends HookConsumerWidget {
builder: (context, controller) { builder: (context, controller) {
return ListView.builder( return ListView.builder(
controller: controller, controller: controller,
padding: EdgeInsets.only( itemCount: containers.length,
bottom: floatingActionButtonBottomInset(context),
),
itemCount: containers.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == 0) { if (index == 0) {
return ListTileTheme( return ListTileTheme(
@@ -108,9 +104,6 @@ class ContainerSelectionScreen extends HookConsumerWidget {
), ),
), ),
loading: () => ListView.builder( loading: () => ListView.builder(
padding: EdgeInsets.only(
bottom: floatingActionButtonBottomInset(context),
),
itemCount: 3, itemCount: 3,
itemBuilder: (context, index) => ContainerListTile( itemBuilder: (context, index) => ContainerListTile(
ContainerData(id: Namespace.nil.value, color: Colors.transparent), ContainerData(id: Namespace.nil.value, color: Colors.transparent),
@@ -26,7 +26,6 @@ import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart'; import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
class ProfileListScreen extends HookConsumerWidget { class ProfileListScreen extends HookConsumerWidget {
const ProfileListScreen({super.key}); const ProfileListScreen({super.key});
@@ -50,9 +49,6 @@ class ProfileListScreen extends HookConsumerWidget {
body: usersAsync.when( body: usersAsync.when(
skipLoadingOnReload: true, skipLoadingOnReload: true,
data: (profiles) => ListView.builder( data: (profiles) => ListView.builder(
padding: EdgeInsets.only(
bottom: floatingActionButtonBottomInset(context),
),
itemCount: profiles.length, itemCount: profiles.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final profile = profiles[index]; final profile = profiles[index];
@@ -26,7 +26,6 @@ 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/controllers/fetch_articles.dart';
import 'package:weblibre/features/web_feed/presentation/widgets/feed_card.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/failure_widget.dart';
import 'package:weblibre/presentation/widgets/floating_action_button_inset.dart';
class FeedListScreen extends HookConsumerWidget { class FeedListScreen extends HookConsumerWidget {
const FeedListScreen({super.key}); const FeedListScreen({super.key});
@@ -77,9 +76,6 @@ class FeedListScreen extends HookConsumerWidget {
.fetchAllArticles(); .fetchAllArticles();
}, },
child: ListView.builder( child: ListView.builder(
padding: EdgeInsets.only(
bottom: floatingActionButtonBottomInset(context),
),
itemCount: feeds.length, itemCount: feeds.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
return FeedCard(feed: feeds[i]); return FeedCard(feed: feeds[i]);
@@ -1,34 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}