Add over/under payment UI
This commit is contained in:
parent
64f5fc0abd
commit
433d4fa3f2
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]
|
||||
,
|
||||
)
|
||||
,
|
||||
)
|
||||
,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue