Dashboard

This commit is contained in:
Hillel Coren 2020-07-22 12:31:56 +03:00
parent 52496d15ae
commit 01e291df56
8 changed files with 76 additions and 57 deletions

View File

@ -61,3 +61,36 @@ List<PaymentEntity> _recentPayments(
return payments; return payments;
} }
var memoizedUpcomingQuotes = memo1((BuiltMap<String, InvoiceEntity> quoteMap) =>
_upcomingQuotes(quoteMap: quoteMap));
List<InvoiceEntity> _upcomingQuotes(
{BuiltMap<String, InvoiceEntity> quoteMap}) {
final quotes = <InvoiceEntity>[];
quoteMap.forEach((index, quote) {
if (quote.isUpcoming) {
quotes.add(quote);
}
});
quotes.sort((quoteA, quoteB) => quoteA.dueDate.compareTo(quoteB.dueDate));
return quotes;
}
var memoizedExpiredQuotes = memo1((BuiltMap<String, InvoiceEntity> quoteMap) =>
_expiredQuotes(quoteMap: quoteMap));
List<InvoiceEntity> _expiredQuotes({BuiltMap<String, InvoiceEntity> quoteMap}) {
final quotes = <InvoiceEntity>[];
quoteMap.forEach((index, quote) {
if (quote.isPastDue) {
quotes.add(quote);
}
});
quotes.sort((quoteA, quoteB) => quoteA.dueDate.compareTo(quoteB.dueDate));
return quotes;
}

View File

@ -29,6 +29,14 @@ abstract class QuoteState implements Built<QuoteState, QuoteStateBuilder> {
BuiltList<String> get list; BuiltList<String> get list;
InvoiceEntity get(String quoteId) {
if (map.containsKey(quoteId)) {
return map[quoteId];
} else {
return InvoiceEntity(id: quoteId, entityType: EntityType.quote);
}
}
QuoteState loadQuotes(BuiltList<InvoiceEntity> quotes) { QuoteState loadQuotes(BuiltList<InvoiceEntity> quotes) {
final map = Map<String, InvoiceEntity>.fromIterable( final map = Map<String, InvoiceEntity>.fromIterable(
quotes, quotes,

View File

@ -92,8 +92,10 @@ class DismissibleEntity extends StatelessWidget {
), ),
], ],
child: SelectedIndicator( child: SelectedIndicator(
isSelected: isSelected: isSelected &&
isSelected && !isMultiselect && !entity.entityType.isSetting, showCheckbox &&
!isMultiselect &&
!entity.entityType.isSetting,
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: 60, minHeight: 60,

View File

@ -9,6 +9,7 @@ import 'package:invoiceninja_flutter/ui/app/help_text.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_list_item.dart'; import 'package:invoiceninja_flutter/ui/invoice/invoice_list_item.dart';
import 'package:invoiceninja_flutter/ui/payment/payment_list_item.dart'; import 'package:invoiceninja_flutter/ui/payment/payment_list_item.dart';
import 'package:invoiceninja_flutter/ui/quote/quote_list_item.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
class SidebarScaffold extends StatelessWidget { class SidebarScaffold extends StatelessWidget {
@ -185,37 +186,37 @@ class _QuoteSidebar extends StatelessWidget {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context); final store = StoreProvider.of<AppState>(context);
final state = store.state; final state = store.state;
final upcomingInvoices = memoizedUpcomingInvoices(state.invoiceState.map); final upcomingQuotes = memoizedUpcomingQuotes(state.quoteState.map);
final pastDueInvoices = memoizedPastDueInvoices(state.invoiceState.map); final expriedQuotes = memoizedExpiredQuotes(state.quoteState.map);
final selectedIds = state.uiState.selectedEntities[EntityType.invoice]; final selectedIds = state.uiState.selectedEntities[EntityType.quote];
return _DashboardSidebar( return _DashboardSidebar(
entityType: EntityType.invoice, entityType: EntityType.quote,
label1: localization.upcomingInvoices + label1: localization.upcomingQuotes +
(upcomingInvoices.isNotEmpty ? ' (${upcomingInvoices.length})' : ''), (upcomingQuotes.isNotEmpty ? ' (${upcomingQuotes.length})' : ''),
list1: upcomingInvoices.isEmpty list1: upcomingQuotes.isEmpty
? null ? null
: ListView.separated( : ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: upcomingInvoices.length, itemCount: upcomingQuotes.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceListItem( return QuoteListItem(
invoice: upcomingInvoices[index], quote: upcomingQuotes[index],
showCheckbox: false, showCheckbox: false,
); );
}, },
separatorBuilder: (context, index) => ListDivider(), separatorBuilder: (context, index) => ListDivider(),
), ),
label2: localization.pastDueInvoices + label2: localization.pastDueInvoices +
(pastDueInvoices.isNotEmpty ? ' (${pastDueInvoices.length})' : ''), (expriedQuotes.isNotEmpty ? ' (${expriedQuotes.length})' : ''),
list2: pastDueInvoices.isEmpty list2: expriedQuotes.isEmpty
? null ? null
: ListView.separated( : ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: pastDueInvoices.length, itemCount: expriedQuotes.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceListItem( return QuoteListItem(
invoice: pastDueInvoices[index], quote: expriedQuotes[index],
showCheckbox: false, showCheckbox: false,
); );
}, },
@ -230,8 +231,8 @@ class _QuoteSidebar extends StatelessWidget {
shrinkWrap: true, shrinkWrap: true,
itemCount: selectedIds?.length, itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceListItem( return QuoteListItem(
invoice: state.invoiceState.get(selectedIds[index]), quote: state.quoteState.get(selectedIds[index]),
showCheckbox: false, showCheckbox: false,
); );
}, },

View File

@ -59,7 +59,6 @@ class InvoiceListItem extends StatelessWidget {
return DismissibleEntity( return DismissibleEntity(
isSelected: isDesktop(context) && isSelected: isDesktop(context) &&
showCheckbox &&
invoice.id == invoice.id ==
(uiState.isEditing (uiState.isEditing
? invoiceUIState.editing.id ? invoiceUIState.editing.id

View File

@ -55,7 +55,6 @@ class PaymentListItem extends StatelessWidget {
return DismissibleEntity( return DismissibleEntity(
isSelected: isDesktop(context) && isSelected: isDesktop(context) &&
showCheckbox &&
payment.id == payment.id ==
(uiState.isEditing (uiState.isEditing
? paymentUIState.editing.id ? paymentUIState.editing.id

View File

@ -14,33 +14,24 @@ import 'package:invoiceninja_flutter/utils/localization.dart';
class QuoteListItem extends StatelessWidget { class QuoteListItem extends StatelessWidget {
const QuoteListItem({ const QuoteListItem({
@required this.user,
@required this.quote, @required this.quote,
@required this.client, this.filter,
@required this.filter, this.showCheckbox = true,
this.onTap,
this.onLongPress,
this.onCheckboxChanged,
this.isChecked = false,
}); });
final UserEntity user;
final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
final InvoiceEntity quote; final InvoiceEntity quote;
final ClientEntity client;
final String filter; final String filter;
final Function(bool) onCheckboxChanged; final bool showCheckbox;
final bool isChecked;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = StoreProvider.of<AppState>(context).state; final state = StoreProvider.of<AppState>(context).state;
final client = state.clientState.get(quote.clientId);
final uiState = state.uiState; final uiState = state.uiState;
final quoteUIState = uiState.quoteUIState; final quoteUIState = uiState.quoteUIState;
final listUIState = state.getUIState(quote.entityType).listUIState; final listUIState = state.getUIState(quote.entityType).listUIState;
final isInMultiselect = listUIState.isInMultiselect(); final isInMultiselect = showCheckbox && listUIState.isInMultiselect();
final showCheckbox = onCheckboxChanged != null || isInMultiselect; final isChecked = isInMultiselect && listUIState.isSelected(quote.id);
final textStyle = TextStyle(fontSize: 16); final textStyle = TextStyle(fontSize: 16);
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final textColor = Theme.of(context).textTheme.bodyText1.color; final textColor = Theme.of(context).textTheme.bodyText1.color;
@ -66,17 +57,14 @@ class QuoteListItem extends StatelessWidget {
? quoteUIState.editing.id ? quoteUIState.editing.id
: quoteUIState.selectedId), : quoteUIState.selectedId),
userCompany: state.userCompany, userCompany: state.userCompany,
showCheckbox: showCheckbox,
entity: quote, entity: quote,
child: LayoutBuilder( child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
return constraints.maxWidth > kTableListWidthCutoff return constraints.maxWidth > kTableListWidthCutoff
? InkWell( ? InkWell(
onTap: () => onTap != null onTap: () => selectEntity(entity: quote, context: context),
? onTap() onLongPress: () => selectEntity(
: selectEntity(entity: quote, context: context),
onLongPress: () => onLongPress != null
? onLongPress()
: selectEntity(
entity: quote, context: context, longPress: true), entity: quote, context: context, longPress: true),
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@ -96,8 +84,7 @@ class QuoteListItem extends StatelessWidget {
value: isChecked, value: isChecked,
materialTapTargetSize: materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap, MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onChanged: (value) => null,
onCheckboxChanged(value),
activeColor: activeColor:
Theme.of(context).accentColor, Theme.of(context).accentColor,
), ),
@ -170,12 +157,8 @@ class QuoteListItem extends StatelessWidget {
), ),
) )
: ListTile( : ListTile(
onTap: () => onTap != null onTap: () => selectEntity(entity: quote, context: context),
? onTap() onLongPress: () => selectEntity(
: selectEntity(entity: quote, context: context),
onLongPress: () => onLongPress != null
? onLongPress()
: selectEntity(
entity: quote, context: context, longPress: true), entity: quote, context: context, longPress: true),
leading: showCheckbox leading: showCheckbox
? IgnorePointer( ? IgnorePointer(
@ -184,7 +167,7 @@ class QuoteListItem extends StatelessWidget {
value: isChecked, value: isChecked,
materialTapTargetSize: materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap, MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value), onChanged: (value) => null,
activeColor: Theme.of(context).accentColor, activeColor: Theme.of(context).accentColor,
), ),
) )

View File

@ -27,7 +27,6 @@ class QuoteListBuilder extends StatelessWidget {
return StoreConnector<AppState, QuoteListVM>( return StoreConnector<AppState, QuoteListVM>(
converter: QuoteListVM.fromStore, converter: QuoteListVM.fromStore,
builder: (context, viewModel) { builder: (context, viewModel) {
final state = viewModel.state;
return EntityList( return EntityList(
entityType: EntityType.quote, entityType: EntityType.quote,
presenter: QuotePresenter(), presenter: QuotePresenter(),
@ -41,15 +40,10 @@ class QuoteListBuilder extends StatelessWidget {
itemBuilder: (BuildContext context, index) { itemBuilder: (BuildContext context, index) {
final invoiceId = viewModel.invoiceList[index]; final invoiceId = viewModel.invoiceList[index];
final invoice = viewModel.invoiceMap[invoiceId]; final invoice = viewModel.invoiceMap[invoiceId];
final listState = state.getListState(EntityType.quote);
final isInMultiselect = listState.isInMultiselect();
return QuoteListItem( return QuoteListItem(
user: state.user,
filter: viewModel.filter, filter: viewModel.filter,
quote: invoice, quote: invoice,
client: viewModel.clientMap[invoice.clientId] ?? ClientEntity(),
isChecked: isInMultiselect && listState.isSelected(invoice.id),
); );
}); });
}, },