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..fd96de23a 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(); + externalScrollController = ScrollController(); if (widget._pageModel.viewBehavior.onResume != null) { ScreenController().executeActionWithScope( context, _scopeManager, widget._pageModel.viewBehavior.onResume!, @@ -212,6 +213,7 @@ class PageState extends State @override void initState() { + externalScrollController = ScrollController(); WidgetsBinding.instance.addObserver(this); _scopeManager = ScopeManager( widget._initialDataContext @@ -253,6 +255,7 @@ class PageState extends State // has completely rendered. This will be sufficient for most use case widget.onRendered(); }); + } // build the root widget @@ -270,6 +273,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 +507,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 +550,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 +625,7 @@ class PageState extends State return getBody(hasAppBar); } - Widget buildScrollablePageContent(bool hasDrawer) { + Widget buildScrollablePageContent(bool hasDrawer) { List slivers = []; externalScrollController = ScrollController(); // appBar @@ -631,6 +645,27 @@ class PageState extends State slivers: slivers, ); } +Widget buildScrollablePageContentWithCollapsableHeader(bool hasDrawer) { + externalScrollController = ScrollController(); + + 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 +1029,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..bccbc5b32 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 = externalScrollController; 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; + }); } }