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/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:

View File

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

View File

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

View File

@ -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: [],

View File

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

View File

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