avoid using builders

This commit is contained in:
Fabian Freund
2025-12-15 21:07:32 +01:00
parent e7a9d03bbc
commit b3a3b9930b
5 changed files with 575 additions and 595 deletions
@@ -52,6 +52,112 @@ import 'package:weblibre/features/geckoview/features/readerview/presentation/wid
import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
class _TabBar extends HookConsumerWidget {
final ValueNotifier<bool> showAppBar;
final ValueNotifier<PersistentBottomSheetController?> sheetController;
const _TabBar({required this.showAppBar, required this.sheetController});
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabId = ref.watch(selectedTabProvider);
final displayedSheet = ref.watch(bottomSheetControllerProvider);
final tabInFullScreen = ref.watch(
selectedTabStateProvider.select((value) => value?.isFullScreen ?? false),
);
final autoHideTabBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.autoHideTabBar,
),
);
if (!autoHideTabBar) {
return Visibility(
visible: !tabInFullScreen,
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
);
}
final appBarVisible = useValueListenable(showAppBar);
final diffAcc = useRef(0.0);
void resetHiddenState() {
showAppBar.value = true;
diffAcc.value = 0.0;
}
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
resetHiddenState();
});
return null;
}, [tabId]);
useOnAppLifecycleStateChange((previous, current) {
if (current == AppLifecycleState.resumed) {
resetHiddenState();
}
});
ref.listen(tabStateProvider(tabId).select((value) => value?.isLoading), (
previous,
next,
) {
if (next == true) {
resetHiddenState();
}
});
ref.listen(tabStateProvider(tabId).select((value) => value?.historyState), (
previous,
next,
) {
if (next != null && previous != null) {
if (previous != next) {
resetHiddenState();
}
}
});
ref.listen(tabScrollYProvider(tabId, const Duration(milliseconds: 50)), (
previous,
next,
) {
if (previous?.value != null && next.value != null) {
final diff = previous!.value! - next.value!;
if (diff < 0) {
if (diffAcc.value > 0) {
diffAcc.value = 0.0;
}
diffAcc.value += diff;
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
showAppBar.value = false;
}
} else if (diff > 0) {
if (diffAcc.value < 0) {
diffAcc.value = 0.0;
}
diffAcc.value += diff;
if (diffAcc.value.abs() > (kToolbarHeight / 2)) {
resetHiddenState();
}
}
}
});
return Visibility(
visible:
sheetController.value != null || (!tabInFullScreen && appBarVisible),
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
);
}
}
class BrowserScreen extends HookConsumerWidget { class BrowserScreen extends HookConsumerWidget {
const BrowserScreen({super.key}); const BrowserScreen({super.key});
@@ -114,107 +220,9 @@ class BrowserScreen extends HookConsumerWidget {
//This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely //This causes issues with a non dismissable barrier pushed, we ahve our own barrier and this does seem to have issues when dismissing, so disable it completely
return null; return null;
}, },
bottomNavigationBar: HookConsumer( bottomNavigationBar: _TabBar(
builder: (context, ref, child) { showAppBar: showAppBar,
final tabId = ref.watch(selectedTabProvider); sheetController: sheetController,
final displayedSheet = ref.watch(bottomSheetControllerProvider);
final tabInFullScreen = ref.watch(
selectedTabStateProvider.select(
(value) => value?.isFullScreen ?? false,
),
);
final autoHideTabBar = ref.watch(
generalSettingsWithDefaultsProvider.select(
(value) => value.autoHideTabBar,
),
);
if (!autoHideTabBar) {
return Visibility(
visible: !tabInFullScreen,
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
);
}
final appBarVisible = useValueListenable(showAppBar);
final diffAcc = useRef(0.0);
void resetHiddenState() {
showAppBar.value = true;
diffAcc.value = 0.0;
}
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
resetHiddenState();
});
return null;
}, [tabId]);
useOnAppLifecycleStateChange((previous, current) {
if (current == AppLifecycleState.resumed) {
resetHiddenState();
}
});
ref.listen(
tabStateProvider(tabId).select((value) => value?.isLoading),
(previous, next) {
if (next == true) {
resetHiddenState();
}
},
);
ref.listen(
tabStateProvider(tabId).select((value) => value?.historyState),
(previous, next) {
if (next != null && previous != null) {
if (previous != next) {
resetHiddenState();
}
}
},
);
ref.listen(
tabScrollYProvider(tabId, const Duration(milliseconds: 50)),
(previous, next) {
if (previous?.value != null && next.value != null) {
final diff = previous!.value! - next.value!;
if (diff < 0) {
if (diffAcc.value > 0) {
diffAcc.value = 0.0;
}
diffAcc.value += diff;
if (diffAcc.value.abs() > kToolbarHeight * 1.5) {
showAppBar.value = false;
}
} else if (diff > 0) {
if (diffAcc.value < 0) {
diffAcc.value = 0.0;
}
diffAcc.value += diff;
if (diffAcc.value.abs() > (kToolbarHeight / 2)) {
resetHiddenState();
}
}
}
},
);
return Visibility(
visible:
sheetController.value != null ||
(!tabInFullScreen && appBarVisible),
child: BrowserBottomAppBar(displayedSheet: displayedSheet),
);
},
), ),
body: _Browser( body: _Browser(
overlayController: overlayController, overlayController: overlayController,
@@ -370,45 +370,14 @@ class _TabViewHeader extends HookConsumerWidget {
} }
} }
class ViewTabsWidget extends HookConsumerWidget { class _TabView extends HookConsumerWidget {
final ScrollController scrollController; final ScrollController scrollController;
final DraggableScrollableController? draggableScrollableController;
final VoidCallback onClose; final VoidCallback onClose;
final bool showNewTabFab;
const ViewTabsWidget({ const _TabView({required this.scrollController, required this.onClose});
required this.onClose,
required this.scrollController,
this.draggableScrollableController,
required this.showNewTabFab,
super.key,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Stack(
alignment: Alignment.bottomRight,
children: [
NestedScrollView(
physics: const NeverScrollableScrollPhysics(),
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverToBoxAdapter(
child:
draggableScrollableController.mapNotNull(
(draggableScrollableController) =>
DraggableScrollableHeader(
controller: draggableScrollableController,
child: _TabViewHeader(
onClose: onClose,
treeViewEnabled: false,
),
),
) ??
_TabViewHeader(onClose: onClose, treeViewEnabled: false),
),
],
body: HookConsumer(
builder: (context, ref, child) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
final containerId = ref.watch(selectedContainerProvider); final containerId = ref.watch(selectedContainerProvider);
@@ -418,16 +387,12 @@ class ViewTabsWidget extends HookConsumerWidget {
searchPartition: TabSearchPartition.preview, searchPartition: TabSearchPartition.preview,
// ignore: document_ignores using fast equatable // ignore: document_ignores using fast equatable
// ignore: provider_parameters // ignore: provider_parameters
containerFilter: ContainerFilterById( containerFilter: ContainerFilterById(containerId: containerId),
containerId: containerId,
),
groupTrees: false, groupTrees: false,
), ),
); );
final tabSuggestionsEnabled = ref.watch( final tabSuggestionsEnabled = ref.watch(tabSuggestionsControllerProvider);
tabSuggestionsControllerProvider,
);
final suggestedTabEntities = tabSuggestionsEnabled final suggestedTabEntities = tabSuggestionsEnabled
? ref.watch(suggestedTabEntitiesProvider(containerId)) ? ref.watch(suggestedTabEntitiesProvider(containerId))
@@ -509,10 +474,7 @@ class ViewTabsWidget extends HookConsumerWidget {
ref.read(willAcceptDropProvider.notifier).clear(); ref.read(willAcceptDropProvider.notifier).clear();
}, },
onReorderPositions: (positions) async { onReorderPositions: (positions) async {
assert( assert(positions.length == 1, 'Not ready for multiple reorders');
positions.length == 1,
'Not ready for multiple reorders',
);
final oldIndex = positions.first.oldIndex; final oldIndex = positions.first.oldIndex;
final newIndex = positions.first.newIndex; final newIndex = positions.first.newIndex;
@@ -533,24 +495,19 @@ class ViewTabsWidget extends HookConsumerWidget {
final String key; final String key;
if (newIndex <= 0) { if (newIndex <= 0) {
key = await containerRepository.getLeadingOrderKey( key = await containerRepository.getLeadingOrderKey(containerId);
containerId, } else if (newIndex >= filteredTabEntities.value.length - 1) {
);
} else if (newIndex >=
filteredTabEntities.value.length - 1) {
key = await containerRepository.getTrailingOrderKey( key = await containerRepository.getTrailingOrderKey(
containerId, containerId,
); );
} else { } else {
if (newIndex < oldIndex) { if (newIndex < oldIndex) {
key = (await containerRepository key = (await containerRepository.getOrderKeyAfterTab(
.getOrderKeyAfterTab(
filteredTabEntities.value[newIndex - 1].tabId, filteredTabEntities.value[newIndex - 1].tabId,
containerId, containerId,
))!; ))!;
} else { } else {
key = await containerRepository key = await containerRepository.getOrderKeyBeforeTab(
.getOrderKeyBeforeTab(
filteredTabEntities.value[newIndex + 1].tabId, filteredTabEntities.value[newIndex + 1].tabId,
containerId, containerId,
); );
@@ -579,16 +536,12 @@ class ViewTabsWidget extends HookConsumerWidget {
tab = CustomDraggable( tab = CustomDraggable(
key: Key(entity.tabId), key: Key(entity.tabId),
data: TabDragData(entity.tabId), data: TabDragData(entity.tabId),
child: _TabDraggable( child: _TabDraggable(entity: entity, onClose: onClose),
entity: entity,
onClose: onClose,
),
); );
} else { } else {
final suggestedIndex = final suggestedIndex =
index - filteredTabEntities.value.length; index - filteredTabEntities.value.length;
final entity = final entity = suggestedTabEntities.value[suggestedIndex];
suggestedTabEntities.value[suggestedIndex];
tab = CustomDraggable( tab = CustomDraggable(
key: Key('suggested_${entity.tabId}'), key: Key('suggested_${entity.tabId}'),
@@ -610,9 +563,48 @@ class ViewTabsWidget extends HookConsumerWidget {
}, },
), ),
); );
}, }
}
class ViewTabsWidget extends HookConsumerWidget {
final ScrollController scrollController;
final DraggableScrollableController? draggableScrollableController;
final VoidCallback onClose;
final bool showNewTabFab;
const ViewTabsWidget({
required this.onClose,
required this.scrollController,
this.draggableScrollableController,
required this.showNewTabFab,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Stack(
alignment: Alignment.bottomRight,
children: [
NestedScrollView(
physics: const NeverScrollableScrollPhysics(),
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverToBoxAdapter(
child:
draggableScrollableController.mapNotNull(
(draggableScrollableController) =>
DraggableScrollableHeader(
controller: draggableScrollableController,
child: _TabViewHeader(
onClose: onClose,
treeViewEnabled: false,
), ),
), ),
) ??
_TabViewHeader(onClose: onClose, treeViewEnabled: false),
),
],
body: _TabView(scrollController: scrollController, onClose: onClose),
),
if (showNewTabFab) if (showNewTabFab)
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -38,14 +38,12 @@ class ContainerListScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Containers')),
body: HookConsumer(
builder: (context, ref, child) {
final containersAsync = ref.watch(watchContainersWithCountProvider); final containersAsync = ref.watch(watchContainersWithCountProvider);
final selectedContainer = ref.watch(selectedContainerProvider); final selectedContainer = ref.watch(selectedContainerProvider);
return Skeletonizer( return Scaffold(
appBar: AppBar(title: const Text('Containers')),
body: Skeletonizer(
enabled: containersAsync.isLoading, enabled: containersAsync.isLoading,
child: containersAsync.when( child: containersAsync.when(
skipLoadingOnReload: true, skipLoadingOnReload: true,
@@ -73,9 +71,7 @@ class ContainerListScreen extends HookConsumerWidget {
result == result ==
SetContainerResult.successHasProxy) { SetContainerResult.successHasProxy) {
await ref await ref
.read( .read(startProxyControllerProvider.notifier)
startProxyControllerProvider.notifier,
)
.maybeStartProxy(context); .maybeStartProxy(context);
} }
}, },
@@ -150,8 +146,6 @@ class ContainerListScreen extends HookConsumerWidget {
), ),
), ),
), ),
);
},
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
@@ -36,13 +36,10 @@ class ContainerSelectionScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final containersAsync = ref.watch(watchContainersWithCountProvider);
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Select Container')), appBar: AppBar(title: const Text('Select Container')),
body: HookConsumer( body: Skeletonizer(
builder: (context, ref, child) {
final containersAsync = ref.watch(watchContainersWithCountProvider);
return Skeletonizer(
enabled: containersAsync.isLoading, enabled: containersAsync.isLoading,
child: containersAsync.when( child: containersAsync.when(
skipLoadingOnReload: true, skipLoadingOnReload: true,
@@ -75,8 +72,6 @@ class ContainerSelectionScreen extends HookConsumerWidget {
), ),
), ),
), ),
);
},
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
@@ -37,14 +37,12 @@ class FeedArticleListScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
HookConsumer(
builder: (context, ref, child) {
final tags = ref.watch(articleFilterProvider); final tags = ref.watch(articleFilterProvider);
final articlesAsync = ref.watch(
// ignore: provider_parameters
filteredArticleListProvider(feedId),
);
final feedTitle = ref.watch( final feedTitle = ref.watch(
feedDataProvider( feedDataProvider(
feedId, feedId,
@@ -75,7 +73,12 @@ class FeedArticleListScreen extends HookConsumerWidget {
return height; return height;
}, [tags.isNotEmpty]); }, [tags.isNotEmpty]);
return SliverAppBar( return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
floating: true, floating: true,
title: Text(feedTitle ?? 'Articles'), title: Text(feedTitle ?? 'Articles'),
bottom: PreferredSize( bottom: PreferredSize(
@@ -99,8 +102,7 @@ class FeedArticleListScreen extends HookConsumerWidget {
) )
: SpeechToTextButton( : SpeechToTextButton(
onTextReceived: (data) { onTextReceived: (data) {
searchTextController.text = data searchTextController.text = data.toString();
.toString();
}, },
), ),
), ),
@@ -148,19 +150,10 @@ class FeedArticleListScreen extends HookConsumerWidget {
), ),
), ),
), ),
);
},
), ),
]; ];
}, },
body: Consumer( body: articlesAsync.when(
builder: (context, ref, child) {
final articlesAsync = ref.watch(
// ignore: provider_parameters
filteredArticleListProvider(feedId),
);
return articlesAsync.when(
skipLoadingOnReload: true, skipLoadingOnReload: true,
data: (articles) { data: (articles) {
return RefreshIndicator( return RefreshIndicator(
@@ -199,8 +192,6 @@ class FeedArticleListScreen extends HookConsumerWidget {
), ),
), ),
loading: () => const SizedBox.shrink(), loading: () => const SizedBox.shrink(),
);
},
), ),
), ),
); );