Flutter upgrade

This commit is contained in:
Hillel Coren 2023-12-17 13:18:02 +02:00
parent d5388e5567
commit b1a7444a61
6 changed files with 440 additions and 464 deletions

View File

@ -123,353 +123,341 @@ class EditScaffold extends StatelessWidget {
final showOverflow = isDesktop(context) && state.isFullScreen;
return WillPopScope(
onWillPop: () async {
return true;
return CallbackShortcuts(
bindings: <ShortcutActivator, VoidCallback>{
const SingleActivator(LogicalKeyboardKey.keyS, control: true): () =>
onSavePressed!(context),
},
child: CallbackShortcuts(
bindings: <ShortcutActivator, VoidCallback>{
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<void>(
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<EntityAction>(
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<EntityAction>(
value: action,
child: Row(
children: <Widget>[
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<void>(
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<EntityAction>(
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<EntityAction>(
child: Row(
children: <Widget>[
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<EntityAction>(
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<EntityAction>(
value: action,
child: Row(
children: <Widget>[
Icon(getEntityActionIcon(action),
color: Theme.of(context)
.colorScheme
.secondary,
),
SizedBox(width: 16.0),
Text(AppLocalization.of(context)!
.lookup(action.toString())),
],
),
value: action,
))
.whereType<PopupMenuEntry<EntityAction>>()
.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<EntityAction>(
icon: Icon(
Icons.more_vert,
//size: iconSize,
//color: color,
),
itemBuilder: (BuildContext context) => [
...actions!
.map((action) => action == null
? PopupMenuDivider()
: PopupMenuItem<EntityAction>(
child: Row(
children: <Widget>[
Icon(
getEntityActionIcon(action),
color: Theme.of(context)
.colorScheme
.secondary,
),
SizedBox(width: 16.0),
Text(AppLocalization.of(context)!
.lookup(action.toString())),
],
),
value: action,
))
.whereType<PopupMenuEntry<EntityAction>>()
.toList()
],
onSelected: (action) =>
onActionPressed!(context, action),
enabled: isEnabled,
)
],
bottom: isFullscreen && isDesktop(context)
? null
: appBarBottom as PreferredSizeWidget?,
),
bottomNavigationBar: bottomNavigationBar,
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: floatingActionButton,
),
),
);

View File

@ -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(

View File

@ -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(

View File

@ -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,
),
),
);

View File

@ -373,29 +373,26 @@ class _DashboardScreenState extends State<DashboardScreen>
),
);
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;
}
}

View File

@ -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