This commit is contained in:
Hillel Coren 2018-09-02 22:34:04 -07:00
parent a81cb5891f
commit d04386974b
7 changed files with 193 additions and 84 deletions

View File

@ -198,3 +198,10 @@ class FilterPaymentsByCustom2 implements PersistUI {
FilterPaymentsByCustom2(this.value); FilterPaymentsByCustom2(this.value);
} }
class FilterPaymentsByClient implements PersistUI {
final int clientId;
FilterPaymentsByClient([this.clientId]);
}

View File

@ -68,17 +68,27 @@ class ClientOverview extends StatelessWidget {
EntityListTile( EntityListTile(
icon: FontAwesomeIcons.filePdfO, icon: FontAwesomeIcons.filePdfO,
title: localization.invoices, title: localization.invoices,
onTap: () => viewModel.onInvoicesPressed(context), onTap: () => viewModel.onEntityPressed(context, EntityType.invoice),
subtitle: memoizedInvoiceStatsForClient( subtitle: memoizedInvoiceStatsForClient(
client.id, client.id,
state.invoiceState.map, state.invoiceState.map,
localization.active, localization.active,
localization.archived), localization.archived),
), ),
EntityListTile(
icon: FontAwesomeIcons.creditCard,
title: localization.payments,
onTap: () => viewModel.onEntityPressed(context, EntityType.payment),
subtitle: memoizedQuoteStatsForClient(
client.id,
state.quoteState.map,
localization.active,
localization.archived),
),
company.isModuleEnabled(EntityType.quote) ? EntityListTile( company.isModuleEnabled(EntityType.quote) ? EntityListTile(
icon: FontAwesomeIcons.fileAltO, icon: FontAwesomeIcons.fileAltO,
title: localization.quotes, title: localization.quotes,
onTap: () => viewModel.onQuotesPressed(context), onTap: () => viewModel.onEntityPressed(context, EntityType.quote),
subtitle: memoizedQuoteStatsForClient( subtitle: memoizedQuoteStatsForClient(
client.id, client.id,
state.quoteState.map, state.quoteState.map,

View File

@ -3,6 +3,7 @@ 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/invoice/invoice_actions.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart'; import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/client/client_screen.dart'; import 'package:invoiceninja_flutter/ui/client/client_screen.dart';
@ -42,8 +43,7 @@ class ClientViewVM {
final Function(BuildContext, EntityAction) onActionSelected; final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext) onEditPressed; final Function(BuildContext) onEditPressed;
final Function onBackPressed; final Function onBackPressed;
final Function(BuildContext) onInvoicesPressed; final Function(BuildContext, EntityType) onEntityPressed;
final Function(BuildContext) onQuotesPressed;
final Function(BuildContext, bool) onRefreshed; final Function(BuildContext, bool) onRefreshed;
final bool isSaving; final bool isSaving;
final bool isLoading; final bool isLoading;
@ -53,9 +53,8 @@ class ClientViewVM {
@required this.client, @required this.client,
@required this.company, @required this.company,
@required this.onActionSelected, @required this.onActionSelected,
@required this.onEntityPressed,
@required this.onEditPressed, @required this.onEditPressed,
@required this.onInvoicesPressed,
@required this.onQuotesPressed,
@required this.onBackPressed, @required this.onBackPressed,
@required this.isSaving, @required this.isSaving,
@required this.isLoading, @required this.isLoading,
@ -94,13 +93,21 @@ class ClientViewVM {
))); )));
}); });
}, },
onInvoicesPressed: (BuildContext context) { onEntityPressed: (BuildContext context, EntityType entityType) {
switch (entityType) {
case EntityType.invoice:
store.dispatch(FilterInvoicesByClient(client.id)); store.dispatch(FilterInvoicesByClient(client.id));
store.dispatch(ViewInvoiceList(context)); store.dispatch(ViewInvoiceList(context));
}, break;
onQuotesPressed: (BuildContext context) { case EntityType.quote:
store.dispatch(FilterQuotesByClient(client.id)); store.dispatch(FilterQuotesByClient(client.id));
store.dispatch(ViewQuoteList(context)); store.dispatch(ViewQuoteList(context));
break;
case EntityType.payment:
store.dispatch(FilterPaymentsByClient(client.id));
store.dispatch(ViewPaymentList(context));
break;
}
}, },
onRefreshed: (context, loadActivities) => onRefreshed: (context, loadActivities) =>
_handleRefresh(context, loadActivities), _handleRefresh(context, loadActivities),

View File

@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/payment_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart'; import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
@ -15,32 +16,21 @@ class PaymentList extends StatelessWidget {
@required this.viewModel, @required this.viewModel,
}) : super(key: key); }) : super(key: key);
@override void _showMenu(
Widget build(BuildContext context) { BuildContext context, PaymentEntity payment, ClientEntity client) async {
if (!viewModel.isLoaded) {
return LoadingIndicator();
} else if (viewModel.paymentList.isEmpty) {
return Opacity(
opacity: 0.5,
child: Center(
child: Text(
AppLocalization.of(context).noRecordsFound,
style: TextStyle(
fontSize: 18.0,
),
),
),
);
}
return _buildListView(context);
}
void _showMenu(BuildContext context, PaymentEntity payment) async {
final user = viewModel.user; final user = viewModel.user;
final message = await showDialog<String>( final message = await showDialog<String>(
context: context, context: context,
builder: (BuildContext context) => SimpleDialog(children: <Widget>[ builder: (BuildContext context) => SimpleDialog(children: <Widget>[
user.canEditEntity(payment) && client.hasEmailAddress
? ListTile(
leading: Icon(Icons.send),
title: Text(AppLocalization.of(context).email),
onTap: () => viewModel.onEntityAction(
context, payment, EntityAction.email),
)
: Container(),
Divider(),
user.canEditEntity(payment) && !payment.isActive user.canEditEntity(payment) && !payment.isActive
? ListTile( ? ListTile(
leading: Icon(Icons.restore), leading: Icon(Icons.restore),
@ -74,29 +64,96 @@ class PaymentList extends StatelessWidget {
} }
} }
Widget _buildListView(BuildContext context) { @override
return RefreshIndicator( Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final listState = viewModel.listState;
final filteredClientId = listState.filterClientId;
final filteredClient =
filteredClientId != null ? viewModel.clientMap[filteredClientId] : null;
return Column(
children: <Widget>[
filteredClient != null
? Material(
color: Colors.orangeAccent,
elevation: 6.0,
child: InkWell(
onTap: () => viewModel.onViewClientFilterPressed(context),
child: Row(
children: <Widget>[
SizedBox(width: 18.0),
Expanded(
child: Text(
localization.clientsPayments.replaceFirst(
':client', filteredClient.displayName),
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
),
onPressed: () => viewModel.onClearClientFilterPressed(),
)
],
),
),
)
: Container(),
Expanded(
child: !viewModel.isLoaded
? LoadingIndicator()
: RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context), onRefresh: () => viewModel.onRefreshed(context),
child: ListView.builder( child: viewModel.paymentList.isEmpty
? Opacity(
opacity: 0.5,
child: Center(
child: Text(
AppLocalization.of(context).noRecordsFound,
style: TextStyle(
fontSize: 18.0,
),
),
),
)
: ListView.builder(
shrinkWrap: true,
itemCount: viewModel.paymentList.length, itemCount: viewModel.paymentList.length,
itemBuilder: (BuildContext context, index) { itemBuilder: (BuildContext context, index) {
final paymentId = viewModel.paymentList[index]; final paymentId = viewModel.paymentList[index];
final payment = viewModel.paymentMap[paymentId]; final payment = viewModel.paymentMap[paymentId];
return Column(children: <Widget>[ final client =
viewModel.clientMap[payment.clientId];
return Column(
children: <Widget>[
PaymentListItem( PaymentListItem(
user: viewModel.user, user: viewModel.user,
filter: viewModel.filter, filter: viewModel.filter,
payment: payment, payment: payment,
onDismissed: (DismissDirection direction) => onDismissed: (DismissDirection direction) =>
viewModel.onDismissed(context, payment, direction), viewModel.onDismissed(
onTap: () => viewModel.onPaymentTap(context, payment), context, payment, direction),
onLongPress: () => _showMenu(context, payment), onTap: () =>
viewModel.onPaymentTap(context, payment),
onLongPress: () =>
_showMenu(context, payment, client),
), ),
Divider( Divider(
height: 1.0, height: 1.0,
), ),
]); ],
}), );
},
),
),
),
],
); );
} }
} }

View File

@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -31,20 +33,25 @@ class PaymentListBuilder extends StatelessWidget {
class PaymentListVM { class PaymentListVM {
final UserEntity user; final UserEntity user;
final ListUIState listState;
final List<int> paymentList; final List<int> paymentList;
final BuiltMap<int, PaymentEntity> paymentMap; final BuiltMap<int, PaymentEntity> paymentMap;
final BuiltMap<int, ClientEntity> clientMap;
final String filter; final String filter;
final bool isLoading; final bool isLoading;
final bool isLoaded; final bool isLoaded;
final Function(BuildContext, PaymentEntity) onPaymentTap; final Function(BuildContext, PaymentEntity) onPaymentTap;
final Function(BuildContext, PaymentEntity, DismissDirection) onDismissed; final Function(BuildContext, PaymentEntity, DismissDirection) onDismissed;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function onClearClientFilterPressed;
final Function(BuildContext) onViewClientFilterPressed;
final Function(BuildContext, PaymentEntity, EntityAction) onEntityAction; final Function(BuildContext, PaymentEntity, EntityAction) onEntityAction;
PaymentListVM({ PaymentListVM({
@required this.user, @required this.user,
@required this.paymentList, @required this.paymentList,
@required this.paymentMap, @required this.paymentMap,
@required this.clientMap,
@required this.filter, @required this.filter,
@required this.isLoading, @required this.isLoading,
@required this.isLoaded, @required this.isLoaded,
@ -52,6 +59,9 @@ class PaymentListVM {
@required this.onDismissed, @required this.onDismissed,
@required this.onRefreshed, @required this.onRefreshed,
@required this.onEntityAction, @required this.onEntityAction,
@required this.onClearClientFilterPressed,
@required this.onViewClientFilterPressed,
@required this.listState,
}); });
static PaymentListVM fromStore(Store<AppState> store) { static PaymentListVM fromStore(Store<AppState> store) {
@ -72,9 +82,11 @@ class PaymentListVM {
paymentList: memoizedFilteredPaymentList(state.paymentState.map, paymentList: memoizedFilteredPaymentList(state.paymentState.map,
state.paymentState.list, state.paymentListState), state.paymentState.list, state.paymentListState),
paymentMap: state.paymentState.map, paymentMap: state.paymentState.map,
clientMap: state.clientState.map,
isLoading: state.isLoading, isLoading: state.isLoading,
isLoaded: state.paymentState.isLoaded, isLoaded: state.paymentState.isLoaded,
filter: state.paymentUIState.listUIState.filter, filter: state.paymentUIState.listUIState.filter,
listState: state.paymentListState,
onPaymentTap: (context, payment) { onPaymentTap: (context, payment) {
store.dispatch(ViewPayment(paymentId: payment.id, context: context)); store.dispatch(ViewPayment(paymentId: payment.id, context: context));
}, },
@ -100,6 +112,12 @@ class PaymentListVM {
break; break;
} }
}, },
onClearClientFilterPressed: () =>
store.dispatch(FilterPaymentsByClient()),
onViewClientFilterPressed: (BuildContext context) => store.dispatch(
ViewClient(
clientId: state.paymentListState.filterClientId,
context: context)),
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onDismissed: (BuildContext context, PaymentEntity payment, onDismissed: (BuildContext context, PaymentEntity payment,
DismissDirection direction) { DismissDirection direction) {

View File

@ -153,6 +153,7 @@ class AppLocalization {
'please_enter_an_invoice_number': 'Please enter an invoice number', 'please_enter_an_invoice_number': 'Please enter an invoice number',
'please_enter_a_quote_number': 'Please enter a quote number', 'please_enter_a_quote_number': 'Please enter a quote number',
'clients_invoices': ':client\'s invoices', 'clients_invoices': ':client\'s invoices',
'clients_payments': ':client\'s payments',
'past_due': 'Past Due', 'past_due': 'Past Due',
'draft': 'Draft', 'draft': 'Draft',
'sent': 'Sent', 'sent': 'Sent',
@ -7418,6 +7419,9 @@ class AppLocalization {
String get clientsInvoices => String get clientsInvoices =>
_localizedValues[locale.languageCode]['clients_invoices']; _localizedValues[locale.languageCode]['clients_invoices'];
String get clientsPayments =>
_localizedValues[locale.languageCode]['clients_payments'];
String get pastDue => _localizedValues[locale.languageCode]['past_due']; String get pastDue => _localizedValues[locale.languageCode]['past_due'];
String get draft => _localizedValues[locale.languageCode]['draft']; String get draft => _localizedValues[locale.languageCode]['draft'];

View File

@ -198,3 +198,9 @@ class FilterStubsByCustom2 implements PersistUI {
FilterStubsByCustom2(this.value); FilterStubsByCustom2(this.value);
} }
class FilterStubsByClient implements PersistUI {
final int clientId;
FilterStubsByClient([this.clientId]);
}