Add over/under payment UI

This commit is contained in:
Hillel Coren 2020-09-04 09:20:17 +03:00
parent 64f5fc0abd
commit 433d4fa3f2
6 changed files with 120 additions and 71 deletions

View File

@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/forms/bool_dropdown_button.dart';
import 'package:invoiceninja_flutter/ui/app/help_text.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_list_item.dart';
@ -22,12 +24,27 @@ class CompanyGatewayList extends StatelessWidget {
final state = store.state;
final listUIState = state.uiState.companyGatewayUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
final localization = AppLocalization.of(context);
final settings = viewModel.settings;
return Column(
children: <Widget>[
SizedBox(
height: 32,
),
FormCard(children: [
BoolDropdownButton(
label: localization.allowOverPayment,
value: settings.clientPortalAllowOverPayment,
helpLabel: localization.allowOverPaymentHelp,
onChanged: (value) => viewModel.onSettingsChanged(settings
.rebuild((b) => b..clientPortalAllowOverPayment = value)),
),
BoolDropdownButton(
label: localization.allowUnderPayment,
value: settings.clientPortalAllowUnderPayment,
helpLabel: localization.allowUnderPaymentHelp,
onChanged: (value) => viewModel.onSettingsChanged(settings
.rebuild((b) => b..clientPortalAllowUnderPayment = value)),
),
]),
Flexible(
fit: FlexFit.tight,
child: !viewModel.state.isLoaded &&
@ -58,7 +75,7 @@ class CompanyGatewayList extends StatelessWidget {
return CompanyGatewayListItem(
key: ValueKey(
'__company_gateway_$companyGatewayId'),
user: viewModel.userCompany.user,
user: state.userCompany.user,
filter: viewModel.filter,
companyGateway: companyGateway,
onRemovePressed:

View File

@ -36,6 +36,7 @@ class CompanyGatewayListItem extends StatelessWidget {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final localization = AppLocalization.of(context);
final filterMatch = filter != null && filter.isNotEmpty
? companyGateway.matchesFilterValue(filter)
: null;
@ -53,53 +54,68 @@ class CompanyGatewayListItem extends StatelessWidget {
trailing: onRemovePressed == null
? null
: FlatButton(
child: Text(AppLocalization.of(context).remove),
onPressed: onRemovePressed,
),
child: Text(AppLocalization
.of(context)
.remove),
onPressed: onRemovePressed,
),
leading: showCheckbox
? IgnorePointer(
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
value: isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor,
),
)
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
value: isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme
.of(context)
.accentColor,
),
)
: Icon(Icons.drag_handle),
title: Container(
width: MediaQuery.of(context).size.width,
width: MediaQuery
.of(context)
.size
.width,
child: Row(
children: <Widget>[
Expanded(
child: Text(
companyGateway.label +
(companyGateway.isTestMode
? ' [${AppLocalization.of(context).testMode}]'
: ''),
style: Theme.of(context).textTheme.headline6,
),
),
Text(formatNumber(companyGateway.listDisplayAmount, context),
style: Theme.of(context).textTheme.headline6),
],
),
),
subtitle: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(companyGateway),
],
children: <Widget>[
Expanded(
child: Text(
companyGateway.label,
style: Theme
.of(context)
.textTheme
.headline6,
),
),
Text(formatNumber(companyGateway.listDisplayAmount, context),
style: Theme
.of(context)
.textTheme
.headline6),
],
),
),
subtitle: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (companyGateway.isTestMode)
Text(localization.testMode),
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(companyGateway),
]
,
)
,
)
,
);
}
}

View File

@ -36,16 +36,16 @@ class CompanyGatewayListBuilder extends StatelessWidget {
class CompanyGatewayListVM {
CompanyGatewayListVM({
@required this.state,
@required this.userCompany,
@required this.companyGatewayList,
@required this.companyGatewayMap,
@required this.filter,
@required this.isLoading,
@required this.onCompanyGatewayTap,
@required this.listState,
@required this.onRefreshed,
@required this.onSortChanged,
@required this.onRemovePressed,
@required this.onSettingsChanged,
@required this.settings,
});
static CompanyGatewayListVM fromStore(Store<AppState> store) {
@ -77,11 +77,10 @@ class CompanyGatewayListVM {
return CompanyGatewayListVM(
state: state,
userCompany: state.userCompany,
listState: state.companyGatewayListState,
companyGatewayList: gatewayIds,
settings: state.uiState.settingsUIState.settings,
companyGatewayMap: state.companyGatewayState.map,
isLoading: state.isLoading,
filter: state.companyGatewayUIState.listUIState.filter,
onCompanyGatewayTap: (context, companyGateway) {
if (store.state.companyGatewayListState.isInMultiselect()) {
@ -108,18 +107,20 @@ class CompanyGatewayListVM {
.rebuild((b) => b..companyGatewayIds = gatewayIds.join(','));
store.dispatch(UpdateSettings(settings: settings));
},
onSettingsChanged: (settings) =>
store.dispatch(UpdateSettings(settings: settings)),
);
}
final AppState state;
final UserCompanyEntity userCompany;
final List<String> companyGatewayList;
final BuiltMap<String, CompanyGatewayEntity> companyGatewayMap;
final ListUIState listState;
final String filter;
final bool isLoading;
final Function(BuildContext, CompanyGatewayEntity) onCompanyGatewayTap;
final Function(BuildContext) onRefreshed;
final Function(int, int) onSortChanged;
final Function(String) onRemovePressed;
final SettingsEntity settings;
final Function(SettingsEntity) onSettingsChanged;
}

View File

@ -64,25 +64,23 @@ class CompanyGatewayScreen extends StatelessWidget {
onCancelPressed: (context) =>
store.dispatch(ClearCompanyGatewayMultiselect()),
)
else if (state.companyGatewayState.list.isNotEmpty) ...[
if (state.uiState.settingsUIState.isFiltered) ...[
FlatButton(
child: Text(localization.reset,
style: TextStyle(color: store.state.headerTextColor)),
onPressed: () {
final settings = store.state.uiState.settingsUIState.settings
.rebuild((b) => b..companyGatewayIds = '');
store.dispatch(UpdateSettings(settings: settings));
},
),
SizedBox(width: 10),
],
SaveCancelButtons(
isSaving: state.isSaving,
onSavePressed: viewModel.onSavePressed,
onCancelPressed: (_) => store.dispatch(ResetSettings()),
)
else if (state.uiState.settingsUIState.isFiltered) ...[
FlatButton(
child: Text(localization.reset,
style: TextStyle(color: store.state.headerTextColor)),
onPressed: () {
final settings = store.state.uiState.settingsUIState.settings
.rebuild((b) => b..companyGatewayIds = '');
store.dispatch(UpdateSettings(settings: settings));
},
),
SizedBox(width: 10),
],
SaveCancelButtons(
isSaving: state.isSaving,
onSavePressed: viewModel.onSavePressed,
onCancelPressed: (_) => store.dispatch(ResetSettings()),
)
],
body: CompanyGatewayListBuilder(),
bottomNavigationBar: AppBottomBar(

View File

@ -106,7 +106,7 @@ class _DashboardChartState extends State<DashboardChart> {
);
return Container(
child: FormCard(
child: _FormCard(
children: <Widget>[
Padding(
padding: EdgeInsets.all(16),
@ -233,8 +233,8 @@ class _DashboardChartState extends State<DashboardChart> {
}
}
class FormCard extends StatelessWidget {
const FormCard({
class _FormCard extends StatelessWidget {
const _FormCard({
Key key,
@required this.children,
}) : super(key: key);

View File

@ -15,6 +15,11 @@ mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
// STARTER: lang key - do not remove comment
'allow_over_payment': 'Allow Over Payment',
'allow_over_payment_help': 'Support paying extra to accept tips',
'allow_under_payment': 'Allow Under Payment',
'allow_under_payment_help':
'Support paying at minimum the partial/deposit amount',
'test_mode': 'Test Mode',
'opened': 'opened',
'payment_reconciliation_failure': 'Reconciliation Failure',
@ -4467,6 +4472,18 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get testMode => _localizedValues[localeCode]['test_mode'] ?? '';
String get allowOverPayment =>
_localizedValues[localeCode]['allow_over_payment'] ?? '';
String get allowOverPaymentHelp =>
_localizedValues[localeCode]['allow_over_payment_help'] ?? '';
String get allowUnderPayment =>
_localizedValues[localeCode]['allow_under_payment'] ?? '';
String get allowUnderPaymentHelp =>
_localizedValues[localeCode]['allow_under_payment_help'] ?? '';
String lookup(String key) {
final lookupKey = toSnakeCase(key);