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/material.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/app/app_state.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/help_text.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_list_item.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 state = store.state;
|
||||||
final listUIState = state.uiState.companyGatewayUIState.listUIState;
|
final listUIState = state.uiState.companyGatewayUIState.listUIState;
|
||||||
final isInMultiselect = listUIState.isInMultiselect();
|
final isInMultiselect = listUIState.isInMultiselect();
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
final settings = viewModel.settings;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
FormCard(children: [
|
||||||
height: 32,
|
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(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
child: !viewModel.state.isLoaded &&
|
child: !viewModel.state.isLoaded &&
|
||||||
|
|
@ -58,7 +75,7 @@ class CompanyGatewayList extends StatelessWidget {
|
||||||
return CompanyGatewayListItem(
|
return CompanyGatewayListItem(
|
||||||
key: ValueKey(
|
key: ValueKey(
|
||||||
'__company_gateway_$companyGatewayId'),
|
'__company_gateway_$companyGatewayId'),
|
||||||
user: viewModel.userCompany.user,
|
user: state.userCompany.user,
|
||||||
filter: viewModel.filter,
|
filter: viewModel.filter,
|
||||||
companyGateway: companyGateway,
|
companyGateway: companyGateway,
|
||||||
onRemovePressed:
|
onRemovePressed:
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class CompanyGatewayListItem extends StatelessWidget {
|
||||||
final store = StoreProvider.of<AppState>(context);
|
final store = StoreProvider.of<AppState>(context);
|
||||||
final state = store.state;
|
final state = store.state;
|
||||||
|
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
final filterMatch = filter != null && filter.isNotEmpty
|
final filterMatch = filter != null && filter.isNotEmpty
|
||||||
? companyGateway.matchesFilterValue(filter)
|
? companyGateway.matchesFilterValue(filter)
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -53,7 +54,9 @@ class CompanyGatewayListItem extends StatelessWidget {
|
||||||
trailing: onRemovePressed == null
|
trailing: onRemovePressed == null
|
||||||
? null
|
? null
|
||||||
: FlatButton(
|
: FlatButton(
|
||||||
child: Text(AppLocalization.of(context).remove),
|
child: Text(AppLocalization
|
||||||
|
.of(context)
|
||||||
|
.remove),
|
||||||
onPressed: onRemovePressed,
|
onPressed: onRemovePressed,
|
||||||
),
|
),
|
||||||
leading: showCheckbox
|
leading: showCheckbox
|
||||||
|
|
@ -63,25 +66,33 @@ class CompanyGatewayListItem extends StatelessWidget {
|
||||||
value: isChecked,
|
value: isChecked,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
onChanged: (value) => onCheckboxChanged(value),
|
onChanged: (value) => onCheckboxChanged(value),
|
||||||
activeColor: Theme.of(context).accentColor,
|
activeColor: Theme
|
||||||
|
.of(context)
|
||||||
|
.accentColor,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(Icons.drag_handle),
|
: Icon(Icons.drag_handle),
|
||||||
title: Container(
|
title: Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery
|
||||||
|
.of(context)
|
||||||
|
.size
|
||||||
|
.width,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
companyGateway.label +
|
companyGateway.label,
|
||||||
(companyGateway.isTestMode
|
style: Theme
|
||||||
? ' [${AppLocalization.of(context).testMode}]'
|
.of(context)
|
||||||
: ''),
|
.textTheme
|
||||||
style: Theme.of(context).textTheme.headline6,
|
.headline6,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(formatNumber(companyGateway.listDisplayAmount, context),
|
Text(formatNumber(companyGateway.listDisplayAmount, context),
|
||||||
style: Theme.of(context).textTheme.headline6),
|
style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.headline6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -89,6 +100,8 @@ class CompanyGatewayListItem extends StatelessWidget {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
if (companyGateway.isTestMode)
|
||||||
|
Text(localization.testMode),
|
||||||
subtitle != null && subtitle.isNotEmpty
|
subtitle != null && subtitle.isNotEmpty
|
||||||
? Text(
|
? Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
|
|
@ -97,9 +110,12 @@ class CompanyGatewayListItem extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
EntityStateLabel(companyGateway),
|
EntityStateLabel(companyGateway),
|
||||||
],
|
]
|
||||||
),
|
,
|
||||||
),
|
)
|
||||||
|
,
|
||||||
|
)
|
||||||
|
,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,16 @@ class CompanyGatewayListBuilder extends StatelessWidget {
|
||||||
class CompanyGatewayListVM {
|
class CompanyGatewayListVM {
|
||||||
CompanyGatewayListVM({
|
CompanyGatewayListVM({
|
||||||
@required this.state,
|
@required this.state,
|
||||||
@required this.userCompany,
|
|
||||||
@required this.companyGatewayList,
|
@required this.companyGatewayList,
|
||||||
@required this.companyGatewayMap,
|
@required this.companyGatewayMap,
|
||||||
@required this.filter,
|
@required this.filter,
|
||||||
@required this.isLoading,
|
|
||||||
@required this.onCompanyGatewayTap,
|
@required this.onCompanyGatewayTap,
|
||||||
@required this.listState,
|
@required this.listState,
|
||||||
@required this.onRefreshed,
|
@required this.onRefreshed,
|
||||||
@required this.onSortChanged,
|
@required this.onSortChanged,
|
||||||
@required this.onRemovePressed,
|
@required this.onRemovePressed,
|
||||||
|
@required this.onSettingsChanged,
|
||||||
|
@required this.settings,
|
||||||
});
|
});
|
||||||
|
|
||||||
static CompanyGatewayListVM fromStore(Store<AppState> store) {
|
static CompanyGatewayListVM fromStore(Store<AppState> store) {
|
||||||
|
|
@ -77,11 +77,10 @@ class CompanyGatewayListVM {
|
||||||
|
|
||||||
return CompanyGatewayListVM(
|
return CompanyGatewayListVM(
|
||||||
state: state,
|
state: state,
|
||||||
userCompany: state.userCompany,
|
|
||||||
listState: state.companyGatewayListState,
|
listState: state.companyGatewayListState,
|
||||||
companyGatewayList: gatewayIds,
|
companyGatewayList: gatewayIds,
|
||||||
|
settings: state.uiState.settingsUIState.settings,
|
||||||
companyGatewayMap: state.companyGatewayState.map,
|
companyGatewayMap: state.companyGatewayState.map,
|
||||||
isLoading: state.isLoading,
|
|
||||||
filter: state.companyGatewayUIState.listUIState.filter,
|
filter: state.companyGatewayUIState.listUIState.filter,
|
||||||
onCompanyGatewayTap: (context, companyGateway) {
|
onCompanyGatewayTap: (context, companyGateway) {
|
||||||
if (store.state.companyGatewayListState.isInMultiselect()) {
|
if (store.state.companyGatewayListState.isInMultiselect()) {
|
||||||
|
|
@ -108,18 +107,20 @@ class CompanyGatewayListVM {
|
||||||
.rebuild((b) => b..companyGatewayIds = gatewayIds.join(','));
|
.rebuild((b) => b..companyGatewayIds = gatewayIds.join(','));
|
||||||
store.dispatch(UpdateSettings(settings: settings));
|
store.dispatch(UpdateSettings(settings: settings));
|
||||||
},
|
},
|
||||||
|
onSettingsChanged: (settings) =>
|
||||||
|
store.dispatch(UpdateSettings(settings: settings)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AppState state;
|
final AppState state;
|
||||||
final UserCompanyEntity userCompany;
|
|
||||||
final List<String> companyGatewayList;
|
final List<String> companyGatewayList;
|
||||||
final BuiltMap<String, CompanyGatewayEntity> companyGatewayMap;
|
final BuiltMap<String, CompanyGatewayEntity> companyGatewayMap;
|
||||||
final ListUIState listState;
|
final ListUIState listState;
|
||||||
final String filter;
|
final String filter;
|
||||||
final bool isLoading;
|
|
||||||
final Function(BuildContext, CompanyGatewayEntity) onCompanyGatewayTap;
|
final Function(BuildContext, CompanyGatewayEntity) onCompanyGatewayTap;
|
||||||
final Function(BuildContext) onRefreshed;
|
final Function(BuildContext) onRefreshed;
|
||||||
final Function(int, int) onSortChanged;
|
final Function(int, int) onSortChanged;
|
||||||
final Function(String) onRemovePressed;
|
final Function(String) onRemovePressed;
|
||||||
|
final SettingsEntity settings;
|
||||||
|
final Function(SettingsEntity) onSettingsChanged;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,7 @@ class CompanyGatewayScreen extends StatelessWidget {
|
||||||
onCancelPressed: (context) =>
|
onCancelPressed: (context) =>
|
||||||
store.dispatch(ClearCompanyGatewayMultiselect()),
|
store.dispatch(ClearCompanyGatewayMultiselect()),
|
||||||
)
|
)
|
||||||
else if (state.companyGatewayState.list.isNotEmpty) ...[
|
else if (state.uiState.settingsUIState.isFiltered) ...[
|
||||||
if (state.uiState.settingsUIState.isFiltered) ...[
|
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text(localization.reset,
|
child: Text(localization.reset,
|
||||||
style: TextStyle(color: store.state.headerTextColor)),
|
style: TextStyle(color: store.state.headerTextColor)),
|
||||||
|
|
@ -83,7 +82,6 @@ class CompanyGatewayScreen extends StatelessWidget {
|
||||||
onCancelPressed: (_) => store.dispatch(ResetSettings()),
|
onCancelPressed: (_) => store.dispatch(ResetSettings()),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
],
|
|
||||||
body: CompanyGatewayListBuilder(),
|
body: CompanyGatewayListBuilder(),
|
||||||
bottomNavigationBar: AppBottomBar(
|
bottomNavigationBar: AppBottomBar(
|
||||||
sortFields: [],
|
sortFields: [],
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ class _DashboardChartState extends State<DashboardChart> {
|
||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
child: FormCard(
|
child: _FormCard(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: EdgeInsets.all(16),
|
||||||
|
|
@ -233,8 +233,8 @@ class _DashboardChartState extends State<DashboardChart> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormCard extends StatelessWidget {
|
class _FormCard extends StatelessWidget {
|
||||||
const FormCard({
|
const _FormCard({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.children,
|
@required this.children,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
static final Map<String, Map<String, String>> _localizedValues = {
|
static final Map<String, Map<String, String>> _localizedValues = {
|
||||||
'en': {
|
'en': {
|
||||||
// STARTER: lang key - do not remove comment
|
// 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',
|
'test_mode': 'Test Mode',
|
||||||
'opened': 'opened',
|
'opened': 'opened',
|
||||||
'payment_reconciliation_failure': 'Reconciliation Failure',
|
'payment_reconciliation_failure': 'Reconciliation Failure',
|
||||||
|
|
@ -4467,6 +4472,18 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
|
|
||||||
String get testMode => _localizedValues[localeCode]['test_mode'] ?? '';
|
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) {
|
String lookup(String key) {
|
||||||
final lookupKey = toSnakeCase(key);
|
final lookupKey = toSnakeCase(key);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue