diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index c7e392a66..4c2fab2e8 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -3148,6 +3148,28 @@ } } }, + { + "title": "Navigate View Group", + "type": "object", + "required": [ + "navigateViewGroup" + ], + "properties": { + "navigateViewGroup": { + "type": "object", + "description": "Navigate between the screens in the view group. It is applicable only when inside a ViewGroup", + "required": [ + "viewIndex" + ], + "properties": { + "viewIndex": { + "type": "integer", + "description": "Enter the index of the screen to navigate" + } + } + } + } + }, { "title": "Navigate Modal Screen", "type": "object", diff --git a/lib/framework/action.dart b/lib/framework/action.dart index c52580a6f..38a5568e7 100644 --- a/lib/framework/action.dart +++ b/lib/framework/action.dart @@ -14,6 +14,8 @@ import 'package:ensemble/framework/extensions.dart'; import 'package:ensemble/framework/keychain_manager.dart'; import 'package:ensemble/framework/permissions_manager.dart'; import 'package:ensemble/framework/scope.dart'; +import 'package:ensemble/framework/view/bottom_nav_page_group.dart'; +import 'package:ensemble/framework/view/page_group.dart'; import 'package:ensemble/framework/widget/view_util.dart'; import 'package:ensemble/screen_controller.dart'; import 'package:ensemble/util/utils.dart'; @@ -114,6 +116,24 @@ class NavigateScreenAction extends BaseNavigateScreenAction { } } +class NavigateViewGroupAction extends EnsembleAction { + NavigateViewGroupAction({dynamic viewIndex}) : _viewIndex = viewIndex; + + final dynamic _viewIndex; + + factory NavigateViewGroupAction.from({Map? payload}) { + return NavigateViewGroupAction(viewIndex: payload?['viewIndex']); + } + + @override + Future execute(BuildContext context, ScopeManager scopeManager, + {DataContext? dataContext}) { + PageGroupWidget.getPageController(context)!.jumpToPage(_viewIndex); + bottomNavBarNotifier.updatePage(_viewIndex); + return Future.value(null); + } +} + class NavigateModalScreenAction extends BaseNavigateScreenAction { NavigateModalScreenAction({ super.initiator, @@ -816,6 +836,7 @@ class ClearKeychain extends EnsembleAction { enum ActionType { invokeAPI, navigateScreen, + navigateViewGroup, navigateExternalScreen, navigateModalScreen, showBottomModal, @@ -907,6 +928,8 @@ abstract class EnsembleAction { } else if (actionType == ActionType.navigateExternalScreen) { return NavigateExternalScreen.from( initiator: initiator, payload: payload); + } else if (actionType == ActionType.navigateViewGroup) { + return NavigateViewGroupAction.from(payload: payload); } else if (actionType == ActionType.navigateModalScreen) { return NavigateModalScreenAction.fromYaml( initiator: initiator, payload: payload); diff --git a/lib/framework/view/bottom_nav_page_group.dart b/lib/framework/view/bottom_nav_page_group.dart index 4f12dd923..292a99fda 100644 --- a/lib/framework/view/bottom_nav_page_group.dart +++ b/lib/framework/view/bottom_nav_page_group.dart @@ -15,6 +15,21 @@ import 'package:ensemble/util/utils.dart'; import 'package:ensemble/framework/widget/icon.dart' as ensemble; import 'package:flutter/material.dart'; +class BottomNavBarNotifier extends ChangeNotifier { + int _viewIndex = 0; + + int get viewIndex => _viewIndex; + + void updatePage(int index, {bool isReload = true}) { + _viewIndex = index; + if (isReload) { + notifyListeners(); + } + } +} + +final bottomNavBarNotifier = BottomNavBarNotifier(); + class FABBottomAppBarItem { FABBottomAppBarItem({ required this.icon, @@ -104,6 +119,8 @@ class _BottomNavPageGroupState extends State floatingAlignment = FloatingAlignment.values.byName(fabMenuItem!.floatingAlignment); } + + bottomNavBarNotifier.updatePage(widget.selectedPage, isReload: false); } @override @@ -179,27 +196,33 @@ class _BottomNavPageGroupState extends State final notchColor = Utils.getColor(widget.menu.styles?['notchColor']) ?? Theme.of(context).scaffoldBackgroundColor; - return Scaffold( - resizeToAvoidBottomInset: true, - backgroundColor: notchColor, - bottomNavigationBar: _buildBottomNavBar(), - floatingActionButtonLocation: floatingAlignment == FloatingAlignment.none - ? null - : floatingAlignment.location, - floatingActionButton: _buildFloatingButton(), - body: PageGroupWidget( - scopeManager: widget.scopeManager, - child: widget.menu.reloadView == true - ? widget.children[selectedPage] - : BottomNavPageView( - controller: controller, - children: widget.children, - ), + return PageGroupWidget( + scopeManager: widget.scopeManager, + pageController: PageController(initialPage: widget.selectedPage), + child: Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: notchColor, + bottomNavigationBar: _buildBottomNavBar(), + floatingActionButtonLocation: + floatingAlignment == FloatingAlignment.none + ? null + : floatingAlignment.location, + floatingActionButton: _buildFloatingButton(), + body: Builder( + builder: (context) { + final controller = PageGroupWidget.getPageController(context); + + return BottomNavPageView( + controller: controller ?? PageController(), + children: widget.children, + ); + }, + ), ), ); } - EnsembleBottomAppBar? _buildBottomNavBar() { + Widget? _buildBottomNavBar() { List navItems = []; final unselectedColor = Utils.getColor(widget.menu.styles?['color']) ?? @@ -241,31 +264,41 @@ class _BottomNavPageGroupState extends State ); } - return EnsembleBottomAppBar( - selectedIndex: widget.selectedPage, - backgroundColor: Utils.getColor(widget.menu.styles?['backgroundColor']) ?? - Colors.white, - height: Utils.optionalDouble(widget.menu.styles?['height'] ?? 60), - margin: widget.menu.styles?['margin'], - padding: widget.menu.styles?['padding'], - borderRadius: Utils.getBorderRadius(widget.menu.styles?['borderRadius']) - ?.getValue(), - color: unselectedColor, - selectedColor: selectedColor, - notchedShape: const CircularNotchedRectangle(), - onTabSelected: (index) { - if (widget.menu.reloadView == true) { - setState(() { - selectedPage = index; - }); - } else { - controller.jumpToPage(index); - } + return ListenableBuilder( + listenable: bottomNavBarNotifier, + builder: (context, _) { + final viewIndex = bottomNavBarNotifier.viewIndex; + + return EnsembleBottomAppBar( + key: UniqueKey(), + selectedIndex: viewIndex, + backgroundColor: + Utils.getColor(widget.menu.styles?['backgroundColor']) ?? + Colors.white, + height: Utils.optionalDouble(widget.menu.styles?['height'] ?? 60), + margin: widget.menu.styles?['margin'], + padding: widget.menu.styles?['padding'], + borderRadius: + Utils.getBorderRadius(widget.menu.styles?['borderRadius']) + ?.getValue(), + color: unselectedColor, + selectedColor: selectedColor, + notchedShape: const CircularNotchedRectangle(), + onTabSelected: (index) { + if (widget.menu.reloadView == true) { + setState(() { + selectedPage = index; + }); + } else { + PageGroupWidget.getPageController(context)?.jumpToPage(index); + } + }, + items: navItems, + isFloating: fabMenuItem != null, + floatingAlignment: floatingAlignment, + floatingMargin: floatingMargin, + ); }, - items: navItems, - isFloating: fabMenuItem != null, - floatingAlignment: floatingAlignment, - floatingMargin: floatingMargin, ); } diff --git a/lib/framework/view/page_group.dart b/lib/framework/view/page_group.dart index 314162501..7f8286173 100644 --- a/lib/framework/view/page_group.dart +++ b/lib/framework/view/page_group.dart @@ -43,15 +43,18 @@ class PageGroup extends StatefulWidget { /// We need this because the menu (i.e. drawer) is determined at the PageGroup /// level, but need to be injected under each child Page to render. class PageGroupWidget extends DataScopeWidget { - const PageGroupWidget( - {super.key, - required super.scopeManager, - required super.child, - this.navigationDrawer, - this.navigationEndDrawer}); + const PageGroupWidget({ + super.key, + required super.scopeManager, + required super.child, + this.navigationDrawer, + this.navigationEndDrawer, + this.pageController, + }); final Drawer? navigationDrawer; final Drawer? navigationEndDrawer; + final PageController? pageController; static Drawer? getNavigationDrawer(BuildContext context) => context .dependOnInheritedWidgetOfExactType() @@ -61,6 +64,10 @@ class PageGroupWidget extends DataScopeWidget { .dependOnInheritedWidgetOfExactType() ?.navigationEndDrawer; + static PageController? getPageController(BuildContext context) => context + .dependOnInheritedWidgetOfExactType() + ?.pageController; + /// return the ScopeManager which includes the dataContext /// TODO: have to repeat this function in DataScopeWidget? static ScopeManager? getScope(BuildContext context) { diff --git a/lib/widget/image.dart b/lib/widget/image.dart index a360bacda..eab00bdd9 100644 --- a/lib/widget/image.dart +++ b/lib/widget/image.dart @@ -84,7 +84,6 @@ class ImageController extends BoxController { } class ImageState extends WidgetState { - @override Widget buildWidget(BuildContext context) { String source = widget._controller.source.trim(); @@ -211,7 +210,6 @@ class ImageState extends WidgetState { } return fallbackWidget; } - } class EnsembleImageCacheManager {