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; final showOverflow = isDesktop(context) && state.isFullScreen;
return WillPopScope( return CallbackShortcuts(
onWillPop: () async { bindings: <ShortcutActivator, VoidCallback>{
return true; const SingleActivator(LogicalKeyboardKey.keyS, control: true): () =>
onSavePressed!(context),
}, },
child: CallbackShortcuts( child: FocusTraversalGroup(
bindings: <ShortcutActivator, VoidCallback>{ child: Scaffold(
const SingleActivator(LogicalKeyboardKey.keyS, control: true): () => body: state.companies.isEmpty
onSavePressed!(context), ? LoadingIndicator()
}, : Stack(
child: FocusTraversalGroup( alignment: Alignment.topCenter,
child: Scaffold( children: [
body: state.companies.isEmpty Column(
? LoadingIndicator() children: [
: Stack( if (showUpgradeBanner &&
alignment: Alignment.topCenter, state.userCompany.isOwner &&
children: [ (!isApple() || supportsInAppPurchase()))
Column( InkWell(
children: [ child: IconMessage(
if (showUpgradeBanner && upgradeMessage,
state.userCompany.isOwner && color: Colors.orange.shade800,
(!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));
}
},
), ),
Expanded( onTap: () async {
child: body, if (bannerClick != null) {
), bannerClick();
], } else if (supportsInAppPurchase() &&
), account.canMakeIAP) {
if (state.isSaving) LinearProgressIndicator(), showDialog<void>(
], context: context,
), builder: (context) => UpgradeDialog(),
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();
},
); );
}), } else {
), launchUrl(Uri.parse(
), state.userCompany.ninjaPortalUrl));
), }
], },
),
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) => [ Expanded(
...actions! child: body,
.map((action) => action == null ),
? PopupMenuDivider() ],
: PopupMenuItem<EntityAction>( ),
child: Row( if (state.isSaving) LinearProgressIndicator(),
children: <Widget>[ ],
Icon( ),
getEntityActionIcon(action), 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) color: Theme.of(context)
.colorScheme .colorScheme
.secondary, .secondary),
), SizedBox(width: 16.0),
SizedBox(width: 16.0), Text(AppLocalization.of(context)!
Text(AppLocalization.of(context)! .lookup(action.toString())),
.lookup(action.toString())), ],
], ),
), );
value: action, }).toList();
)) },
.whereType<PopupMenuEntry<EntityAction>>() );
.toList() }),
], ),
onSelected: (action) => ),
onActionPressed!(context, action), ),
enabled: isEnabled, ],
)
],
bottom: isFullscreen && isDesktop(context)
? null
: appBarBottom as PreferredSizeWidget?,
), ),
bottomNavigationBar: bottomNavigationBar, actions: showOverflow
floatingActionButtonLocation: ? []
FloatingActionButtonLocation.endDocked, : [
floatingActionButton: floatingActionButton, 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( return PopScope(
onWillPop: () async { canPop: false,
onPopInvoked: (_) {
store.dispatch(ViewDashboard()); store.dispatch(ViewDashboard());
return false;
}, },
child: FocusTraversalGroup( child: FocusTraversalGroup(
child: Scaffold( child: Scaffold(

View File

@ -258,8 +258,9 @@ class MainScreen extends StatelessWidget {
print('## Error: main screen route $mainRoute not defined'); print('## Error: main screen route $mainRoute not defined');
} }
return WillPopScope( return PopScope(
onWillPop: () async { canPop: false,
onPopInvoked: (_) async {
final state = store.state; final state = store.state;
final historyList = state.historyList; final historyList = state.historyList;
final isEditing = state.uiState.isEditing; final isEditing = state.uiState.isEditing;
@ -268,7 +269,6 @@ class MainScreen extends StatelessWidget {
if (state.uiState.isPreviewing) { if (state.uiState.isPreviewing) {
store.dispatch(PopPreviewStack()); store.dispatch(PopPreviewStack());
return false;
} }
for (int i = index; i < historyList.length; i++) { for (int i = index; i < historyList.length; i++) {
@ -300,38 +300,35 @@ class MainScreen extends StatelessWidget {
if (history == null) { if (history == null) {
store.dispatch(ViewDashboard()); 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: DesktopSessionTimeout(
child: SafeArea( child: SafeArea(

View File

@ -89,73 +89,67 @@ class ViewScaffold extends StatelessWidget {
} }
} }
return WillPopScope( return FocusTraversalGroup(
onWillPop: () async { child: Scaffold(
return true; backgroundColor: Theme.of(context).cardColor,
}, appBar: AppBar(
child: FocusTraversalGroup( centerTitle: false,
child: Scaffold( leading: leading,
backgroundColor: Theme.of(context).cardColor, automaticallyImplyLeading: isMobile(context),
appBar: AppBar( title: CopyToClipboard(
centerTitle: false, value: appBarTitle,
leading: leading, child: Text(appBarTitle!),
automaticallyImplyLeading: isMobile(context), ),
title: CopyToClipboard( bottom: appBarBottom as PreferredSizeWidget?,
value: appBarTitle, actions: entity.isNew
child: Text(appBarTitle!), ? []
), : [
bottom: appBarBottom as PreferredSizeWidget?, if (isSettings && isDesktop(context) && !isFilter)
actions: entity.isNew TextButton(
? [] onPressed: () {
: [ onBackPressed != null
if (isSettings && isDesktop(context) && !isFilter) ? onBackPressed!()
TextButton( : store.dispatch(UpdateCurrentRoute(
onPressed: () { state.uiState.previousRoute));
onBackPressed != null },
? onBackPressed!() child: Text(
: store.dispatch(UpdateCurrentRoute( localization!.back,
state.uiState.previousRoute)); style: TextStyle(color: state.headerTextColor),
}, )),
child: Text( if (isEditable && userCompany.canEditEntity(entity))
localization!.back, Builder(builder: (context) {
style: TextStyle(color: state.headerTextColor), final isDisabled = state.uiState.isEditing &&
)), state.uiState.mainRoute ==
if (isEditable && userCompany.canEditEntity(entity)) state.uiState.filterEntityType.toString();
Builder(builder: (context) {
final isDisabled = state.uiState.isEditing &&
state.uiState.mainRoute ==
state.uiState.filterEntityType.toString();
return AppTextButton( return AppTextButton(
label: localization!.edit, label: localization!.edit,
isInHeader: true, isInHeader: true,
onPressed: isDisabled onPressed: isDisabled
? null ? null
: () { : () {
editEntity(entity: entity); editEntity(entity: entity);
}, },
); );
}), }),
ViewActionMenuButton( ViewActionMenuButton(
isSaving: state.isSaving && !isFilter, isSaving: state.isSaving && !isFilter,
entity: entity, entity: entity,
onSelected: (context, action) => onSelected: (context, action) =>
handleEntityAction(entity, action, autoPop: true), handleEntityAction(entity, action, autoPop: true),
entityActions: entity.getActions( entityActions: entity.getActions(
userCompany: userCompany, userCompany: userCompany,
client: entity is BelongsToClient client: entity is BelongsToClient
? state.clientState ? state.clientState
.map[(entity as BelongsToClient).clientId] .map[(entity as BelongsToClient).clientId]
: null, : null,
),
), ),
], ),
), ],
body: SafeArea( ),
child: entity.isNew body: SafeArea(
? BlankScreen(localization!.noRecordSelected) child:
: body, entity.isNew ? BlankScreen(localization!.noRecordSelected) : body,
),
), ),
), ),
); );

View File

@ -373,29 +373,26 @@ class _DashboardScreenState extends State<DashboardScreen>
), ),
); );
return WillPopScope( return isDesktop(context)
onWillPop: () async => true, ? Row(
child: isDesktop(context) children: [
? Row( Flexible(
children: [ child: mainScaffold,
flex: 3,
),
if (state.dashboardUIState.showSidebar)
Flexible( Flexible(
child: mainScaffold, child: AppBorder(
flex: 3, isLeft: true,
), child: SidebarScaffold(
if (state.dashboardUIState.showSidebar) tabController: _sideTabController,
Flexible(
child: AppBorder(
isLeft: true,
child: SidebarScaffold(
tabController: _sideTabController,
),
), ),
flex: 2,
), ),
], flex: 2,
) ),
: mainScaffold, ],
); )
: mainScaffold;
} }
} }

View File

@ -326,10 +326,10 @@ class ReportsScreen extends StatelessWidget {
), ),
]; ];
return WillPopScope( return PopScope(
onWillPop: () async { canPop: false,
onPopInvoked: (_) {
store.dispatch(ViewDashboard()); store.dispatch(ViewDashboard());
return false;
}, },
child: Scaffold( child: Scaffold(
drawer: isMobile(context) || state.prefState.isMenuFloated drawer: isMobile(context) || state.prefState.isMenuFloated