diff --git a/lib/ui/app/edit_scaffold.dart b/lib/ui/app/edit_scaffold.dart index ba7b9f7b3..0075f4b53 100644 --- a/lib/ui/app/edit_scaffold.dart +++ b/lib/ui/app/edit_scaffold.dart @@ -123,353 +123,341 @@ class EditScaffold extends StatelessWidget { final showOverflow = isDesktop(context) && state.isFullScreen; - return WillPopScope( - onWillPop: () async { - return true; + return CallbackShortcuts( + bindings: { + const SingleActivator(LogicalKeyboardKey.keyS, control: true): () => + onSavePressed!(context), }, - child: CallbackShortcuts( - bindings: { - const SingleActivator(LogicalKeyboardKey.keyS, control: true): () => - onSavePressed!(context), - }, - child: FocusTraversalGroup( - child: Scaffold( - body: state.companies.isEmpty - ? LoadingIndicator() - : Stack( - alignment: Alignment.topCenter, - children: [ - Column( - children: [ - if (showUpgradeBanner && - state.userCompany.isOwner && - (!isApple() || supportsInAppPurchase())) - InkWell( - child: IconMessage( - upgradeMessage, - color: Colors.orange.shade800, - ), - onTap: () async { - if (bannerClick != null) { - bannerClick(); - } else if (supportsInAppPurchase() && - account.canMakeIAP) { - showDialog( - context: context, - builder: (context) => UpgradeDialog(), - ); - } else { - launchUrl(Uri.parse( - state.userCompany.ninjaPortalUrl)); - } - }, + child: FocusTraversalGroup( + child: Scaffold( + body: state.companies.isEmpty + ? LoadingIndicator() + : Stack( + alignment: Alignment.topCenter, + children: [ + Column( + children: [ + if (showUpgradeBanner && + state.userCompany.isOwner && + (!isApple() || supportsInAppPurchase())) + InkWell( + child: IconMessage( + upgradeMessage, + color: Colors.orange.shade800, ), - Expanded( - child: body, - ), - ], - ), - if (state.isSaving) LinearProgressIndicator(), - ], - ), - drawer: isDesktop(context) ? MenuDrawerBuilder() : null, - appBar: AppBar( - centerTitle: false, - automaticallyImplyLeading: isMobile(context), - title: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (showOverflow) - Text(title!) - else - Flexible(child: Text(title!)), - SizedBox(width: 16), - if (isDesktop(context) && - isFullscreen && - entity != null && - entity!.isOld) ...[ - EntityStatusChip( - entity: - state.getEntity(entity!.entityType, entity!.id)), - SizedBox(width: 8), - ], - if (showOverflow) - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: FocusTraversalGroup( - // TODO this is needed as a workaround to prevent - // breaking tab focus traversal - descendantsAreFocusable: false, - child: OverflowView.flexible( - spacing: 8, - children: entityActions.map( - (action) { - String? label; - if (action == EntityAction.save && - saveLabel != null) { - label = saveLabel; - } else { - label = localization.lookup('$action'); - } - - return OutlinedButton( - style: action == EntityAction.save && - isEnabled - ? ButtonStyle( - backgroundColor: - MaterialStateProperty.all(state - .prefState - .colorThemeModel! - .colorSuccess)) - : null, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: - isDesktop(context) ? 60 : 0), - child: isDesktop(context) - ? IconText( - icon: getEntityActionIcon(action), - text: label, - style: state.isSaving - ? null - : action == EntityAction.save - ? textStyle.copyWith( - color: Colors.white) - : textStyle, - ) - : Text(label!, - style: state.isSaving - ? null - : textStyle), - ), - onPressed: state.isSaving - ? null - : () { - if (action == EntityAction.back) { - if (onCancelPressed != null) { - onCancelPressed!(context); - } else { - store.dispatch(ResetSettings()); - } - } else if (action == - EntityAction.save) { - // Clear focus now to prevent un-focus after save from - // marking the form as changed and to hide the keyboard - FocusScope.of(context).unfocus( - disposition: UnfocusDisposition - .previouslyFocusedChild); - - onSavePressed!(context); - } else { - onActionPressed!(context, action); - } - }, - ); - }, - ).toList(), - builder: (context, remaining) { - return PopupMenuButton( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8), - child: isDesktop(context) - ? Row( - children: [ - Text( - localization.more, - style: textStyle, - ), - SizedBox(width: 4), - Icon(Icons.arrow_drop_down, - color: state.headerTextColor), - ], - ) - : Icon(Icons.more_vert), - ), - onSelected: (EntityAction action) { - onActionPressed!(context, action); - }, - itemBuilder: (BuildContext context) { - return entityActions - .toList() - .sublist( - entityActions.length - remaining) - .map((action) { - return PopupMenuItem( - value: action, - child: Row( - children: [ - Icon(getEntityActionIcon(action), - color: Theme.of(context) - .colorScheme - .secondary), - SizedBox(width: 16.0), - Text(AppLocalization.of(context)! - .lookup(action.toString())), - ], - ), - ); - }).toList(); - }, + onTap: () async { + if (bannerClick != null) { + bannerClick(); + } else if (supportsInAppPurchase() && + account.canMakeIAP) { + showDialog( + context: context, + builder: (context) => UpgradeDialog(), ); - }), - ), - ), - ), - ], - ), - actions: showOverflow - ? [] - : [ - if (state.isSaving && isMobile(context)) - Padding( - padding: const EdgeInsets.only(right: 20), - child: Center( - child: SizedBox( - width: 26, - height: 26, - child: - CircularProgressIndicator(color: Colors.white), - )), - ) - else if (isDesktop(context)) - Row( - children: [ - OutlinedButton( - onPressed: state.isSaving - ? null - : () { - if (onCancelPressed != null) { - onCancelPressed!(context); - } else { - store.dispatch(ResetSettings()); - } - }, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: 60), - child: IconText( - icon: getEntityActionIcon(EntityAction.back), - text: (entity != null && - entity!.entityType!.isSetting) - ? localization.back - : localization.cancel, - style: state.isSaving ? null : textStyle, - ), - ), - ), - SizedBox(width: 8), - OutlinedButton( - style: isEnabled - ? ButtonStyle( - backgroundColor: - MaterialStateProperty.all(state - .prefState - .colorThemeModel! - .colorSuccess)) - : null, - onPressed: !isEnabled || - state.isSaving || - onSavePressed == null - ? null - : () { - // Clear focus now to prevent un-focus after save from - // marking the form as changed and to hide the keyboard - FocusScope.of(context).unfocus( - disposition: UnfocusDisposition - .previouslyFocusedChild); - - onSavePressed!(context); - }, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: 60), - child: IconText( - icon: getEntityActionIcon(EntityAction.save), - text: localization.save, - style: state.isSaving - ? null - : textStyle.copyWith(color: Colors.white), - ), - ), - ), - SizedBox(width: 16), - ], - ) - else - SaveCancelButtons( - isEnabled: isEnabled && onSavePressed != null, - isHeader: true, - isCancelEnabled: isCancelEnabled, - saveLabel: saveLabel, - cancelLabel: localization.cancel, - onSavePressed: onSavePressed == null - ? null - : (context) { - // Clear focus now to prevent un-focus after save from - // marking the form as changed and to hide the keyboard - FocusScope.of(context).unfocus( - disposition: UnfocusDisposition - .previouslyFocusedChild); - - onSavePressed!(context); - }, - onCancelPressed: isMobile(context) - ? null - : (context) { - if (onCancelPressed != null) { - onCancelPressed!(context); - } else { - store.dispatch(ResetSettings()); - } - }, - ), - if (actions != null && - actions!.isNotEmpty && - onActionPressed != null) - PopupMenuButton( - icon: Icon( - Icons.more_vert, - //size: iconSize, - //color: color, + } else { + launchUrl(Uri.parse( + state.userCompany.ninjaPortalUrl)); + } + }, ), - itemBuilder: (BuildContext context) => [ - ...actions! - .map((action) => action == null - ? PopupMenuDivider() - : PopupMenuItem( - child: Row( - children: [ - Icon( - getEntityActionIcon(action), + Expanded( + child: body, + ), + ], + ), + if (state.isSaving) LinearProgressIndicator(), + ], + ), + drawer: isDesktop(context) ? MenuDrawerBuilder() : null, + appBar: AppBar( + centerTitle: false, + automaticallyImplyLeading: isMobile(context), + title: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (showOverflow) + Text(title!) + else + Flexible(child: Text(title!)), + SizedBox(width: 16), + if (isDesktop(context) && + isFullscreen && + entity != null && + entity!.isOld) ...[ + EntityStatusChip( + entity: state.getEntity(entity!.entityType, entity!.id)), + SizedBox(width: 8), + ], + if (showOverflow) + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: FocusTraversalGroup( + // TODO this is needed as a workaround to prevent + // breaking tab focus traversal + descendantsAreFocusable: false, + child: OverflowView.flexible( + spacing: 8, + children: entityActions.map( + (action) { + String? label; + if (action == EntityAction.save && + saveLabel != null) { + label = saveLabel; + } else { + label = localization.lookup('$action'); + } + + return OutlinedButton( + style: action == EntityAction.save && + isEnabled + ? ButtonStyle( + backgroundColor: + MaterialStateProperty.all(state + .prefState + .colorThemeModel! + .colorSuccess)) + : null, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: isDesktop(context) ? 60 : 0), + child: isDesktop(context) + ? IconText( + icon: getEntityActionIcon(action), + text: label, + style: state.isSaving + ? null + : action == EntityAction.save + ? textStyle.copyWith( + color: Colors.white) + : textStyle, + ) + : Text(label!, + style: state.isSaving + ? null + : textStyle), + ), + onPressed: state.isSaving + ? null + : () { + if (action == EntityAction.back) { + if (onCancelPressed != null) { + onCancelPressed!(context); + } else { + store.dispatch(ResetSettings()); + } + } else if (action == + EntityAction.save) { + // Clear focus now to prevent un-focus after save from + // marking the form as changed and to hide the keyboard + FocusScope.of(context).unfocus( + disposition: UnfocusDisposition + .previouslyFocusedChild); + + onSavePressed!(context); + } else { + onActionPressed!(context, action); + } + }, + ); + }, + ).toList(), + builder: (context, remaining) { + return PopupMenuButton( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8), + child: isDesktop(context) + ? Row( + children: [ + Text( + localization.more, + style: textStyle, + ), + SizedBox(width: 4), + Icon(Icons.arrow_drop_down, + color: state.headerTextColor), + ], + ) + : Icon(Icons.more_vert), + ), + onSelected: (EntityAction action) { + onActionPressed!(context, action); + }, + itemBuilder: (BuildContext context) { + return entityActions + .toList() + .sublist(entityActions.length - remaining) + .map((action) { + return PopupMenuItem( + value: action, + child: Row( + children: [ + Icon(getEntityActionIcon(action), color: Theme.of(context) .colorScheme - .secondary, - ), - SizedBox(width: 16.0), - Text(AppLocalization.of(context)! - .lookup(action.toString())), - ], - ), - value: action, - )) - .whereType>() - .toList() - ], - onSelected: (action) => - onActionPressed!(context, action), - enabled: isEnabled, - ) - ], - bottom: isFullscreen && isDesktop(context) - ? null - : appBarBottom as PreferredSizeWidget?, + .secondary), + SizedBox(width: 16.0), + Text(AppLocalization.of(context)! + .lookup(action.toString())), + ], + ), + ); + }).toList(); + }, + ); + }), + ), + ), + ), + ], ), - bottomNavigationBar: bottomNavigationBar, - floatingActionButtonLocation: - FloatingActionButtonLocation.endDocked, - floatingActionButton: floatingActionButton, + actions: showOverflow + ? [] + : [ + if (state.isSaving && isMobile(context)) + Padding( + padding: const EdgeInsets.only(right: 20), + child: Center( + child: SizedBox( + width: 26, + height: 26, + child: CircularProgressIndicator(color: Colors.white), + )), + ) + else if (isDesktop(context)) + Row( + children: [ + OutlinedButton( + onPressed: state.isSaving + ? null + : () { + if (onCancelPressed != null) { + onCancelPressed!(context); + } else { + store.dispatch(ResetSettings()); + } + }, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: 60), + child: IconText( + icon: getEntityActionIcon(EntityAction.back), + text: (entity != null && + entity!.entityType!.isSetting) + ? localization.back + : localization.cancel, + style: state.isSaving ? null : textStyle, + ), + ), + ), + SizedBox(width: 8), + OutlinedButton( + style: isEnabled + ? ButtonStyle( + backgroundColor: MaterialStateProperty.all( + state.prefState.colorThemeModel! + .colorSuccess)) + : null, + onPressed: !isEnabled || + state.isSaving || + onSavePressed == null + ? null + : () { + // Clear focus now to prevent un-focus after save from + // marking the form as changed and to hide the keyboard + FocusScope.of(context).unfocus( + disposition: UnfocusDisposition + .previouslyFocusedChild); + + onSavePressed!(context); + }, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: 60), + child: IconText( + icon: getEntityActionIcon(EntityAction.save), + text: localization.save, + style: state.isSaving + ? null + : textStyle.copyWith(color: Colors.white), + ), + ), + ), + SizedBox(width: 16), + ], + ) + else + SaveCancelButtons( + isEnabled: isEnabled && onSavePressed != null, + isHeader: true, + isCancelEnabled: isCancelEnabled, + saveLabel: saveLabel, + cancelLabel: localization.cancel, + onSavePressed: onSavePressed == null + ? null + : (context) { + // Clear focus now to prevent un-focus after save from + // marking the form as changed and to hide the keyboard + FocusScope.of(context).unfocus( + disposition: UnfocusDisposition + .previouslyFocusedChild); + + onSavePressed!(context); + }, + onCancelPressed: isMobile(context) + ? null + : (context) { + if (onCancelPressed != null) { + onCancelPressed!(context); + } else { + store.dispatch(ResetSettings()); + } + }, + ), + if (actions != null && + actions!.isNotEmpty && + onActionPressed != null) + PopupMenuButton( + icon: Icon( + Icons.more_vert, + //size: iconSize, + //color: color, + ), + itemBuilder: (BuildContext context) => [ + ...actions! + .map((action) => action == null + ? PopupMenuDivider() + : PopupMenuItem( + child: Row( + children: [ + Icon( + getEntityActionIcon(action), + color: Theme.of(context) + .colorScheme + .secondary, + ), + SizedBox(width: 16.0), + Text(AppLocalization.of(context)! + .lookup(action.toString())), + ], + ), + value: action, + )) + .whereType>() + .toList() + ], + onSelected: (action) => + onActionPressed!(context, action), + enabled: isEnabled, + ) + ], + bottom: isFullscreen && isDesktop(context) + ? null + : appBarBottom as PreferredSizeWidget?, ), + bottomNavigationBar: bottomNavigationBar, + floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + floatingActionButton: floatingActionButton, ), ), ); diff --git a/lib/ui/app/list_scaffold.dart b/lib/ui/app/list_scaffold.dart index ab086971b..d05ecc2f7 100644 --- a/lib/ui/app/list_scaffold.dart +++ b/lib/ui/app/list_scaffold.dart @@ -124,10 +124,10 @@ class ListScaffold extends StatelessWidget { ], ); - return WillPopScope( - onWillPop: () async { + return PopScope( + canPop: false, + onPopInvoked: (_) { store.dispatch(ViewDashboard()); - return false; }, child: FocusTraversalGroup( child: Scaffold( diff --git a/lib/ui/app/main_screen.dart b/lib/ui/app/main_screen.dart index 323f39ed7..4214eb80c 100644 --- a/lib/ui/app/main_screen.dart +++ b/lib/ui/app/main_screen.dart @@ -258,8 +258,9 @@ class MainScreen extends StatelessWidget { print('## Error: main screen route $mainRoute not defined'); } - return WillPopScope( - onWillPop: () async { + return PopScope( + canPop: false, + onPopInvoked: (_) async { final state = store.state; final historyList = state.historyList; final isEditing = state.uiState.isEditing; @@ -268,7 +269,6 @@ class MainScreen extends StatelessWidget { if (state.uiState.isPreviewing) { store.dispatch(PopPreviewStack()); - return false; } for (int i = index; i < historyList.length; i++) { @@ -300,38 +300,35 @@ class MainScreen extends StatelessWidget { if (history == null) { store.dispatch(ViewDashboard()); - return false; + } else { + switch (history.entityType) { + case EntityType.dashboard: + store.dispatch(ViewDashboard()); + break; + case EntityType.reports: + store.dispatch(ViewReports()); + break; + case EntityType.settings: + store.dispatch(ViewSettings( + section: history.id, + company: state.company, + user: state.user, + tabIndex: 0, + )); + break; + default: + if ((history.id ?? '').isEmpty) { + viewEntitiesByType( + entityType: history.entityType, page: history.page); + } else { + viewEntityById( + entityId: history.id, + entityType: history.entityType, + showError: false, + ); + } + } } - - switch (history.entityType) { - case EntityType.dashboard: - store.dispatch(ViewDashboard()); - break; - case EntityType.reports: - store.dispatch(ViewReports()); - break; - case EntityType.settings: - store.dispatch(ViewSettings( - section: history.id, - company: state.company, - user: state.user, - tabIndex: 0, - )); - break; - default: - if ((history.id ?? '').isEmpty) { - viewEntitiesByType( - entityType: history.entityType, page: history.page); - } else { - viewEntityById( - entityId: history.id, - entityType: history.entityType, - showError: false, - ); - } - } - - return false; }, child: DesktopSessionTimeout( child: SafeArea( diff --git a/lib/ui/app/view_scaffold.dart b/lib/ui/app/view_scaffold.dart index 8c38230a3..9617b4a22 100644 --- a/lib/ui/app/view_scaffold.dart +++ b/lib/ui/app/view_scaffold.dart @@ -89,73 +89,67 @@ class ViewScaffold extends StatelessWidget { } } - return WillPopScope( - onWillPop: () async { - return true; - }, - child: FocusTraversalGroup( - child: Scaffold( - backgroundColor: Theme.of(context).cardColor, - appBar: AppBar( - centerTitle: false, - leading: leading, - automaticallyImplyLeading: isMobile(context), - title: CopyToClipboard( - value: appBarTitle, - child: Text(appBarTitle!), - ), - bottom: appBarBottom as PreferredSizeWidget?, - actions: entity.isNew - ? [] - : [ - if (isSettings && isDesktop(context) && !isFilter) - TextButton( - onPressed: () { - onBackPressed != null - ? onBackPressed!() - : store.dispatch(UpdateCurrentRoute( - state.uiState.previousRoute)); - }, - child: Text( - localization!.back, - style: TextStyle(color: state.headerTextColor), - )), - if (isEditable && userCompany.canEditEntity(entity)) - Builder(builder: (context) { - final isDisabled = state.uiState.isEditing && - state.uiState.mainRoute == - state.uiState.filterEntityType.toString(); + return FocusTraversalGroup( + child: Scaffold( + backgroundColor: Theme.of(context).cardColor, + appBar: AppBar( + centerTitle: false, + leading: leading, + automaticallyImplyLeading: isMobile(context), + title: CopyToClipboard( + value: appBarTitle, + child: Text(appBarTitle!), + ), + bottom: appBarBottom as PreferredSizeWidget?, + actions: entity.isNew + ? [] + : [ + if (isSettings && isDesktop(context) && !isFilter) + TextButton( + onPressed: () { + onBackPressed != null + ? onBackPressed!() + : store.dispatch(UpdateCurrentRoute( + state.uiState.previousRoute)); + }, + child: Text( + localization!.back, + style: TextStyle(color: state.headerTextColor), + )), + if (isEditable && userCompany.canEditEntity(entity)) + Builder(builder: (context) { + final isDisabled = state.uiState.isEditing && + state.uiState.mainRoute == + state.uiState.filterEntityType.toString(); - return AppTextButton( - label: localization!.edit, - isInHeader: true, - onPressed: isDisabled - ? null - : () { - editEntity(entity: entity); - }, - ); - }), - ViewActionMenuButton( - isSaving: state.isSaving && !isFilter, - entity: entity, - onSelected: (context, action) => - handleEntityAction(entity, action, autoPop: true), - entityActions: entity.getActions( - userCompany: userCompany, - client: entity is BelongsToClient - ? state.clientState - .map[(entity as BelongsToClient).clientId] - : null, - ), + return AppTextButton( + label: localization!.edit, + isInHeader: true, + onPressed: isDisabled + ? null + : () { + editEntity(entity: entity); + }, + ); + }), + ViewActionMenuButton( + isSaving: state.isSaving && !isFilter, + entity: entity, + onSelected: (context, action) => + handleEntityAction(entity, action, autoPop: true), + entityActions: entity.getActions( + userCompany: userCompany, + client: entity is BelongsToClient + ? state.clientState + .map[(entity as BelongsToClient).clientId] + : null, ), - ], - ), - body: SafeArea( - child: entity.isNew - ? BlankScreen(localization!.noRecordSelected) - : body, - ), + ), + ], + ), + body: SafeArea( + child: + entity.isNew ? BlankScreen(localization!.noRecordSelected) : body, ), ), ); diff --git a/lib/ui/dashboard/dashboard_screen.dart b/lib/ui/dashboard/dashboard_screen.dart index 7a2129442..070dbc651 100644 --- a/lib/ui/dashboard/dashboard_screen.dart +++ b/lib/ui/dashboard/dashboard_screen.dart @@ -373,29 +373,26 @@ class _DashboardScreenState extends State ), ); - return WillPopScope( - onWillPop: () async => true, - child: isDesktop(context) - ? Row( - children: [ + return isDesktop(context) + ? Row( + children: [ + Flexible( + child: mainScaffold, + flex: 3, + ), + if (state.dashboardUIState.showSidebar) Flexible( - child: mainScaffold, - flex: 3, - ), - if (state.dashboardUIState.showSidebar) - Flexible( - child: AppBorder( - isLeft: true, - child: SidebarScaffold( - tabController: _sideTabController, - ), + child: AppBorder( + isLeft: true, + child: SidebarScaffold( + tabController: _sideTabController, ), - flex: 2, ), - ], - ) - : mainScaffold, - ); + flex: 2, + ), + ], + ) + : mainScaffold; } } diff --git a/lib/ui/reports/reports_screen.dart b/lib/ui/reports/reports_screen.dart index 15877ebd8..41442aced 100644 --- a/lib/ui/reports/reports_screen.dart +++ b/lib/ui/reports/reports_screen.dart @@ -326,10 +326,10 @@ class ReportsScreen extends StatelessWidget { ), ]; - return WillPopScope( - onWillPop: () async { + return PopScope( + canPop: false, + onPopInvoked: (_) { store.dispatch(ViewDashboard()); - return false; }, child: Scaffold( drawer: isMobile(context) || state.prefState.isMenuFloated