From 7a48691de1ea8b80dc713db2ad0b249d5e6d80f8 Mon Sep 17 00:00:00 2001 From: M-Taha-Dev Date: Tue, 24 Jun 2025 18:30:25 +0500 Subject: [PATCH 1/6] added support for hiding header onListViewScroll --- modules/ensemble/lib/ensemble_app.dart | 2 + modules/ensemble/lib/framework/view/page.dart | 63 ++++++++++++++++--- .../lib/layout/helpers/list_view_core.dart | 21 +++++-- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/modules/ensemble/lib/ensemble_app.dart b/modules/ensemble/lib/ensemble_app.dart index f4482329e..33b44e71b 100644 --- a/modules/ensemble/lib/ensemble_app.dart +++ b/modules/ensemble/lib/ensemble_app.dart @@ -38,6 +38,8 @@ const String backgroundBluetoothSubscribeTask = 'backgroundBluetoothSubscribeTas const String ensembleMethodChannelName = 'com.ensembleui.host.platform'; GlobalKey? externalAppNavigateKey; ScrollController? externalScrollController; +Map persistentControllers = {}; +String? currentPageKey; @pragma('vm:entry-point') void callbackDispatcher() { diff --git a/modules/ensemble/lib/framework/view/page.dart b/modules/ensemble/lib/framework/view/page.dart index 84dba503d..6cac2711f 100644 --- a/modules/ensemble/lib/framework/view/page.dart +++ b/modules/ensemble/lib/framework/view/page.dart @@ -93,7 +93,7 @@ class PageState extends State @override bool get wantKeepAlive => true; - + @override void didUpdateWidget(covariant Page oldWidget) { super.didUpdateWidget(oldWidget); @@ -196,6 +196,7 @@ class PageState extends State @override void didPopNext() { super.didPopNext(); + currentPageKey = widget._pageModel.hashCode.toString(); if (widget._pageModel.viewBehavior.onResume != null) { ScreenController().executeActionWithScope( context, _scopeManager, widget._pageModel.viewBehavior.onResume!, @@ -253,6 +254,7 @@ class PageState extends State // has completely rendered. This will be sufficient for most use case widget.onRendered(); }); + } // build the root widget @@ -270,6 +272,7 @@ class PageState extends State // Adding a listener for [viewGroupNotifier] so we can execute // onViewGroupUpdate when change in parent ViewGroup occurs viewGroupNotifier.addListener(executeOnViewGroupUpdate); + } /// This is a callback because we need the widget to be first instantiate @@ -503,7 +506,9 @@ class PageState extends State // whether to usse CustomScrollView for the entire page bool isScrollableView = widget._pageModel.runtimeStyles?['scrollableView'] == true; - + + bool collapsableHeader = widget._pageModel.runtimeStyles?['collapsableHeader'] == true; + bool collapseSafeArea = widget._pageModel.runtimeStyles?['collapseSafeArea'] == true; PreferredSizeWidget? fixedAppBar; if (!isScrollableView) { fixedAppBar = buildFixedAppBar(widget._pageModel, hasDrawer); @@ -544,12 +549,20 @@ class PageState extends State // appBar is inside CustomScrollView if defined appBar: fixedAppBar, - body: FooterLayout( - body: isScrollableView - ? buildScrollablePageContent(hasDrawer) - : buildFixedPageContent(fixedAppBar != null), - footer: footerWidget, - ), +body: FooterLayout( + body: isScrollableView + ? (collapsableHeader == true + ? (collapseSafeArea == true + ? SafeArea( + top: true, + bottom: false, // Let footer handle bottom safe area + child: buildScrollablePageContentWithCollapsableHeader(hasDrawer) + ) + : buildScrollablePageContentWithCollapsableHeader(hasDrawer)) + : buildScrollablePageContent(hasDrawer)) + : buildFixedPageContent(fixedAppBar != null), + footer: footerWidget, +), bottomNavigationBar: _bottomNavBar, drawer: _drawer, endDrawer: _endDrawer, @@ -611,7 +624,7 @@ class PageState extends State return getBody(hasAppBar); } - Widget buildScrollablePageContent(bool hasDrawer) { + Widget buildScrollablePageContent(bool hasDrawer) { List slivers = []; externalScrollController = ScrollController(); // appBar @@ -631,6 +644,37 @@ class PageState extends State slivers: slivers, ); } +Widget buildScrollablePageContentWithCollapsableHeader(bool hasDrawer) { + bool isScrollableView = widget._pageModel.runtimeStyles?['scrollableView'] == true; +if (isScrollableView) { + currentPageKey = widget._pageModel.hashCode.toString(); + if (persistentControllers.containsKey(currentPageKey!)) { + externalScrollController = ScrollController(); + persistentControllers[currentPageKey!] = externalScrollController!; + } else { + externalScrollController = ScrollController(); + persistentControllers[currentPageKey!] = externalScrollController!; + } +} + return NestedScrollView( + controller: externalScrollController, + physics: ClampingScrollPhysics(), + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + List slivers = []; + // Build the AppBar using your existing method + Widget? appBar = buildSliverAppBar(widget._pageModel, hasDrawer); + + // Add AppBar to slivers if it exists and is not manually hidden + if (appBar != null) { + slivers.add(appBar); + } + + return slivers; + }, + body: getBody(true), // Pass true since we have a header structure + ); + + } Widget getBody(bool hasAppBar) { // ignore safe area is only applicable if we don't have an AppBar @@ -994,6 +1038,7 @@ class _AnimatedAppBarState extends State with WidgetsBindingObse collapsedHeight: widget.collapsedBarHeight, expandedHeight: widget.expandedBarHeight, pinned: widget.pinned, + floating: widget.floating, centerTitle: widget.centerTitle, title: widget.animated ? switch (widget.animationType) { diff --git a/modules/ensemble/lib/layout/helpers/list_view_core.dart b/modules/ensemble/lib/layout/helpers/list_view_core.dart index 9d534a64e..da1e4cbed 100644 --- a/modules/ensemble/lib/layout/helpers/list_view_core.dart +++ b/modules/ensemble/lib/layout/helpers/list_view_core.dart @@ -103,6 +103,7 @@ class _ListViewCoreState extends State { late final Debouncer debounce; late final Debouncer _scrollDebouce; late final ScrollController _scrollController; + double _previousOffset = 0.0; int? _lastFetchedIndex; @@ -161,16 +162,24 @@ class _ListViewCoreState extends State { // given the nestedScroll property is set to true and View is Scrollable // Note that we are not using jumpTo to avoid jerky movement of external // Scroll instead we are using animateTo which is smoother than jumpTo + ScrollController? currentExternalScrollController = persistentControllers[currentPageKey]; if (externalScrollController != null && widget.nestedScroll && widget.shrinkWrap) { - final currentOffset = _scrollController.position.pixels; + _scrollController.addListener(() { + + final currentOffset = _scrollController.offset; + final scrollingUp = currentOffset > _previousOffset; + final scrollingDown = currentOffset < _previousOffset; + + if (scrollingUp) { + currentExternalScrollController!.animateTo(100, duration: const Duration(milliseconds: 16), curve: Curves.linear); + } else if (scrollingDown && currentOffset > 0) { + currentExternalScrollController!.jumpTo(0); + } - externalScrollController!.animateTo( - currentOffset, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); + _previousOffset = currentOffset; + }); } } From d935786f0574d35516040234c494fc6c8a9d5fb0 Mon Sep 17 00:00:00 2001 From: M-Taha-Dev Date: Fri, 27 Jun 2025 02:30:18 +0500 Subject: [PATCH 2/6] fixed scroller initialization issue --- modules/ensemble/lib/framework/view/page.dart | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/modules/ensemble/lib/framework/view/page.dart b/modules/ensemble/lib/framework/view/page.dart index 6cac2711f..e753f14bd 100644 --- a/modules/ensemble/lib/framework/view/page.dart +++ b/modules/ensemble/lib/framework/view/page.dart @@ -197,6 +197,8 @@ class PageState extends State void didPopNext() { super.didPopNext(); currentPageKey = widget._pageModel.hashCode.toString(); + externalScrollController = ScrollController(); + persistentControllers[currentPageKey!] = externalScrollController!; if (widget._pageModel.viewBehavior.onResume != null) { ScreenController().executeActionWithScope( context, _scopeManager, widget._pageModel.viewBehavior.onResume!, @@ -645,35 +647,27 @@ body: FooterLayout( ); } Widget buildScrollablePageContentWithCollapsableHeader(bool hasDrawer) { - bool isScrollableView = widget._pageModel.runtimeStyles?['scrollableView'] == true; -if (isScrollableView) { - currentPageKey = widget._pageModel.hashCode.toString(); - if (persistentControllers.containsKey(currentPageKey!)) { - externalScrollController = ScrollController(); - persistentControllers[currentPageKey!] = externalScrollController!; - } else { + externalScrollController = ScrollController(); persistentControllers[currentPageKey!] = externalScrollController!; - } -} - return NestedScrollView( - controller: externalScrollController, - physics: ClampingScrollPhysics(), - headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { - List slivers = []; - // Build the AppBar using your existing method - Widget? appBar = buildSliverAppBar(widget._pageModel, hasDrawer); - - // Add AppBar to slivers if it exists and is not manually hidden - if (appBar != null) { - slivers.add(appBar); - } - - return slivers; - }, - body: getBody(true), // Pass true since we have a header structure - ); + return NestedScrollView( + controller: externalScrollController, + physics: ClampingScrollPhysics(), + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + List slivers = []; + // Build the AppBar using your existing method + Widget? appBar = buildSliverAppBar(widget._pageModel, hasDrawer); + + // Add AppBar to slivers if it exists and is not manually hidden + if (appBar != null) { + slivers.add(appBar); + } + + return slivers; + }, + body: getBody(true), // Pass true since we have a header structure + ); } Widget getBody(bool hasAppBar) { From 2d9d54030def5b5e6c898734623b78bb8e4aec81 Mon Sep 17 00:00:00 2001 From: M-Taha-Dev Date: Fri, 27 Jun 2025 02:41:49 +0500 Subject: [PATCH 3/6] fix 2 --- modules/ensemble/lib/framework/view/page.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/ensemble/lib/framework/view/page.dart b/modules/ensemble/lib/framework/view/page.dart index e753f14bd..d278f7fd5 100644 --- a/modules/ensemble/lib/framework/view/page.dart +++ b/modules/ensemble/lib/framework/view/page.dart @@ -215,6 +215,9 @@ class PageState extends State @override void initState() { + currentPageKey = widget._pageModel.hashCode.toString(); + externalScrollController = ScrollController(); + persistentControllers[currentPageKey!] = externalScrollController!; WidgetsBinding.instance.addObserver(this); _scopeManager = ScopeManager( widget._initialDataContext @@ -647,10 +650,6 @@ body: FooterLayout( ); } Widget buildScrollablePageContentWithCollapsableHeader(bool hasDrawer) { - - externalScrollController = ScrollController(); - persistentControllers[currentPageKey!] = externalScrollController!; - return NestedScrollView( controller: externalScrollController, physics: ClampingScrollPhysics(), From 671661b490ae73da9c9212e6e1adae34557574ab Mon Sep 17 00:00:00 2001 From: M-Taha-Dev Date: Fri, 27 Jun 2025 02:51:35 +0500 Subject: [PATCH 4/6] fix 3 --- modules/ensemble/lib/framework/view/page.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/ensemble/lib/framework/view/page.dart b/modules/ensemble/lib/framework/view/page.dart index d278f7fd5..c3001640e 100644 --- a/modules/ensemble/lib/framework/view/page.dart +++ b/modules/ensemble/lib/framework/view/page.dart @@ -196,9 +196,7 @@ class PageState extends State @override void didPopNext() { super.didPopNext(); - currentPageKey = widget._pageModel.hashCode.toString(); externalScrollController = ScrollController(); - persistentControllers[currentPageKey!] = externalScrollController!; if (widget._pageModel.viewBehavior.onResume != null) { ScreenController().executeActionWithScope( context, _scopeManager, widget._pageModel.viewBehavior.onResume!, @@ -215,9 +213,7 @@ class PageState extends State @override void initState() { - currentPageKey = widget._pageModel.hashCode.toString(); externalScrollController = ScrollController(); - persistentControllers[currentPageKey!] = externalScrollController!; WidgetsBinding.instance.addObserver(this); _scopeManager = ScopeManager( widget._initialDataContext From 1ba0b953cabe07bd4f1ea3c3ab65ac9369417a2a Mon Sep 17 00:00:00 2001 From: M-Taha-Dev Date: Fri, 27 Jun 2025 02:56:15 +0500 Subject: [PATCH 5/6] fix 3 --- modules/ensemble/lib/framework/view/page.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ensemble/lib/framework/view/page.dart b/modules/ensemble/lib/framework/view/page.dart index c3001640e..fd96de23a 100644 --- a/modules/ensemble/lib/framework/view/page.dart +++ b/modules/ensemble/lib/framework/view/page.dart @@ -646,6 +646,8 @@ body: FooterLayout( ); } Widget buildScrollablePageContentWithCollapsableHeader(bool hasDrawer) { + externalScrollController = ScrollController(); + return NestedScrollView( controller: externalScrollController, physics: ClampingScrollPhysics(), From 91484d862847ec01c21e1dd51eacc18290866258 Mon Sep 17 00:00:00 2001 From: M-Taha-Dev Date: Fri, 27 Jun 2025 02:58:44 +0500 Subject: [PATCH 6/6] fix 4 --- modules/ensemble/lib/layout/helpers/list_view_core.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ensemble/lib/layout/helpers/list_view_core.dart b/modules/ensemble/lib/layout/helpers/list_view_core.dart index da1e4cbed..bccbc5b32 100644 --- a/modules/ensemble/lib/layout/helpers/list_view_core.dart +++ b/modules/ensemble/lib/layout/helpers/list_view_core.dart @@ -162,7 +162,7 @@ class _ListViewCoreState extends State { // given the nestedScroll property is set to true and View is Scrollable // Note that we are not using jumpTo to avoid jerky movement of external // Scroll instead we are using animateTo which is smoother than jumpTo - ScrollController? currentExternalScrollController = persistentControllers[currentPageKey]; + ScrollController? currentExternalScrollController = externalScrollController; if (externalScrollController != null && widget.nestedScroll && widget.shrinkWrap) {