Mock data

This commit is contained in:
Hillel Coren 2020-02-24 21:52:18 +02:00
parent 15bc8ef735
commit 6805d58718
4 changed files with 245 additions and 184 deletions

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// This version must be updated in tandem with the pubspec version. // This version must be updated in tandem with the pubspec version.
const String kAppVersion = '2.0.8'; const String kAppVersion = '2.0.13';
const String kSiteUrl = 'https://invoiceninja.com'; const String kSiteUrl = 'https://invoiceninja.com';
//const String kAppUrl = 'https://admin.invoiceninja.com'; //const String kAppUrl = 'https://admin.invoiceninja.com';
const String kAppUrl = 'https://staging.invoicing.co'; const String kAppUrl = 'https://staging.invoicing.co';

View File

@ -254,7 +254,7 @@ const dynamic kMockLogin = '''
"late_fee_endless_percent": 0, "late_fee_endless_percent": 0,
"client_online_payment_notification": true, "client_online_payment_notification": true,
"client_manual_payment_notification": true, "client_manual_payment_notification": true,
"name": "", "name": "Test Account",
"company_logo": "", "company_logo": "",
"website": "", "website": "",
"address1": "", "address1": "",

View File

@ -10,6 +10,7 @@ import 'package:invoiceninja_flutter/ui/app/responsive_padding.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/.env.dart';
class EntityDropdown extends StatefulWidget { class EntityDropdown extends StatefulWidget {
const EntityDropdown({ const EntityDropdown({
@ -111,7 +112,8 @@ class _EntityDropdownState extends State<EntityDropdown> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isNotMobile(context)) { // TODO remove DEMO_MODE check
if (isNotMobile(context) && !Config.DEMO_MODE) {
return TypeAheadFormField<String>( return TypeAheadFormField<String>(
noItemsFoundBuilder: (context) => SizedBox(), noItemsFoundBuilder: (context) => SizedBox(),
suggestionsBoxDecoration: SuggestionsBoxDecoration( suggestionsBoxDecoration: SuggestionsBoxDecoration(

View File

@ -25,6 +25,7 @@ import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/utils/strings.dart'; import 'package:invoiceninja_flutter/utils/strings.dart';
import 'package:invoiceninja_flutter/.env.dart';
class ReportsScreen extends StatelessWidget { class ReportsScreen extends StatelessWidget {
const ReportsScreen({ const ReportsScreen({
@ -47,8 +48,8 @@ class ReportsScreen extends StatelessWidget {
final hasCustomDate = reportsState.filters.keys.where((column) { final hasCustomDate = reportsState.filters.keys.where((column) {
final filter = reportsState.filters[column]; final filter = reportsState.filters[column];
return (getReportColumnType(column, context) == return (getReportColumnType(column, context) ==
ReportColumnType.dateTime || ReportColumnType.dateTime ||
getReportColumnType(column, context) == ReportColumnType.date) && getReportColumnType(column, context) == ReportColumnType.date) &&
filter == DateRange.custom.toString(); filter == DateRange.custom.toString();
}).isNotEmpty; }).isNotEmpty;
@ -68,10 +69,11 @@ class ReportsScreen extends StatelessWidget {
leading: isMobile(context) || state.prefState.isMenuFloated leading: isMobile(context) || state.prefState.isMenuFloated
? null ? null
: IconButton( : IconButton(
icon: Icon(Icons.menu), icon: Icon(Icons.menu),
onPressed: () => store onPressed: () =>
.dispatch(UserSettingsChanged(sidebar: AppSidebar.menu)), store
), .dispatch(UserSettingsChanged(sidebar: AppSidebar.menu)),
),
title: Row( title: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
@ -113,23 +115,26 @@ class ReportsScreen extends StatelessWidget {
), ),
if (isMobile(context) || !state.prefState.isHistoryVisible) if (isMobile(context) || !state.prefState.isHistoryVisible)
Builder( Builder(
builder: (context) => IconButton( builder: (context) =>
icon: Icon(Icons.menu), IconButton(
onPressed: () { icon: Icon(Icons.menu),
if (isMobile(context) || state.prefState.isHistoryFloated) { onPressed: () {
Scaffold.of(context).openEndDrawer(); if (isMobile(context) ||
} else { state.prefState.isHistoryFloated) {
store.dispatch( Scaffold.of(context).openEndDrawer();
UserSettingsChanged(sidebar: AppSidebar.history)); } else {
} store.dispatch(
}, UserSettingsChanged(sidebar: AppSidebar.history));
), }
},
),
), ),
], ],
), ),
body: ListView( body: ListView(
key: ValueKey( key: ValueKey(
'${viewModel.state.isSaving}_${reportsState.report}_${reportsState.group}'), '${viewModel.state.isSaving}_${reportsState.report}_${reportsState
.group}'),
children: <Widget>[ children: <Widget>[
Flex( Flex(
direction: isMobile(context) ? Axis.vertical : Axis.horizontal, direction: isMobile(context) ? Axis.vertical : Axis.horizontal,
@ -159,10 +164,11 @@ class ReportsScreen extends StatelessWidget {
//kReportTaxRate, //kReportTaxRate,
//kReportQuote, //kReportQuote,
] ]
.map((report) => DropdownMenuItem( .map((report) =>
value: report, DropdownMenuItem(
child: Text(localization.lookup(report)), value: report,
)) child: Text(localization.lookup(report)),
))
.toList(), .toList(),
), ),
if (hasCustomDate) ...[ if (hasCustomDate) ...[
@ -170,8 +176,9 @@ class ReportsScreen extends StatelessWidget {
labelText: localization.startDate, labelText: localization.startDate,
selectedDate: reportsState.customStartDate, selectedDate: reportsState.customStartDate,
allowClearing: true, allowClearing: true,
onSelected: (date) => viewModel.onSettingsChanged( onSelected: (date) =>
customStartDate: date), viewModel.onSettingsChanged(
customStartDate: date),
), ),
DatePicker( DatePicker(
labelText: localization.endDate, labelText: localization.endDate,
@ -198,11 +205,11 @@ class ReportsScreen extends StatelessWidget {
}, },
items: reportResult.columns items: reportResult.columns
.where((column) => .where((column) =>
getReportColumnType(column, context) != getReportColumnType(column, context) !=
ReportColumnType.number) ReportColumnType.number)
.map((column) { .map((column) {
final columnTitle = final columnTitle =
state.company.getCustomFieldLabel(column); state.company.getCustomFieldLabel(column);
return DropdownMenuItem( return DropdownMenuItem(
child: Text(columnTitle.isEmpty child: Text(columnTitle.isEmpty
? localization.lookup(column) ? localization.lookup(column)
@ -212,7 +219,7 @@ class ReportsScreen extends StatelessWidget {
}).toList(), }).toList(),
), ),
if (getReportColumnType(reportsState.group, context) == if (getReportColumnType(reportsState.group, context) ==
ReportColumnType.dateTime || ReportColumnType.dateTime ||
getReportColumnType(reportsState.group, context) == getReportColumnType(reportsState.group, context) ==
ReportColumnType.date) ReportColumnType.date)
AppDropdownButton<String>( AppDropdownButton<String>(
@ -252,12 +259,13 @@ class ReportsScreen extends StatelessWidget {
}, },
items: reportResult.columns items: reportResult.columns
.where((column) => .where((column) =>
getReportColumnType(column, context) == getReportColumnType(column, context) ==
ReportColumnType.number) ReportColumnType.number)
.map((column) => DropdownMenuItem( .map((column) =>
child: Text(localization.lookup(column)), DropdownMenuItem(
value: column, child: Text(localization.lookup(column)),
)) value: column,
))
.toList(), .toList(),
), ),
], ],
@ -267,7 +275,8 @@ class ReportsScreen extends StatelessWidget {
), ),
ReportDataTable( ReportDataTable(
key: ValueKey( key: ValueKey(
'${viewModel.state.isSaving}_${reportsState.group}_${reportsState.selectedGroup}'), '${viewModel.state.isSaving}_${reportsState
.group}_${reportsState.selectedGroup}'),
viewModel: viewModel, viewModel: viewModel,
) )
], ],
@ -288,7 +297,7 @@ class ReportDataTable extends StatefulWidget {
class _ReportDataTableState extends State<ReportDataTable> { class _ReportDataTableState extends State<ReportDataTable> {
final Map<String, Map<String, TextEditingController>> final Map<String, Map<String, TextEditingController>>
_textEditingControllers = {}; _textEditingControllers = {};
ReportDataTableSource dataTableSource; ReportDataTableSource dataTableSource;
@override @override
@ -394,15 +403,16 @@ class _ReportDataTableState extends State<ReportDataTable> {
children: <Widget>[ children: <Widget>[
DataTable( DataTable(
sortColumnIndex: reportSettings.sortTotalsIndex != null && sortColumnIndex: reportSettings.sortTotalsIndex != null &&
reportResult.columns.length > reportResult.columns.length >
reportSettings.sortTotalsIndex reportSettings.sortTotalsIndex
? reportSettings.sortTotalsIndex ? reportSettings.sortTotalsIndex
: null, : null,
sortAscending: reportSettings.sortTotalsAscending, sortAscending: reportSettings.sortTotalsAscending,
columns: reportResult.totalColumns( columns: reportResult.totalColumns(
context, context,
(index, ascending) => widget.viewModel (index, ascending) =>
.onReportTotalsSorted(index, ascending)), widget.viewModel
.onReportTotalsSorted(index, ascending)),
rows: reportResult.totalRows(context), rows: reportResult.totalRows(context),
), ),
], ],
@ -425,8 +435,9 @@ class _ReportDataTableState extends State<ReportDataTable> {
sortAscending: reportSettings.sortAscending, sortAscending: reportSettings.sortAscending,
columns: reportResult.tableColumns( columns: reportResult.tableColumns(
context, context,
(index, ascending) => (index, ascending) =>
widget.viewModel.onReportSorted(sortedColumns[index], ascending)), widget.viewModel
.onReportSorted(sortedColumns[index], ascending)),
source: dataTableSource, source: dataTableSource,
), ),
) )
@ -435,7 +446,6 @@ class _ReportDataTableState extends State<ReportDataTable> {
} }
} }
enum ReportColumnType { enum ReportColumnType {
string, string,
dateTime, dateTime,
@ -518,7 +528,7 @@ class ReportDataTableSource extends DataTableSource {
return reportResult.tableFilters( return reportResult.tableFilters(
context, context,
textEditingControllers[viewModel.reportState.report], textEditingControllers[viewModel.reportState.report],
(column, value) => onFilterChanged(column, value)); (column, value) => onFilterChanged(column, value));
} else { } else {
return reportResult.tableRow(context, viewModel, index); return reportResult.tableRow(context, viewModel, index);
} }
@ -667,8 +677,8 @@ class ReportResult {
return data; return data;
} }
List<DataColumn> tableColumns( List<DataColumn> tableColumns(BuildContext context,
BuildContext context, Function(int, bool) onSortCallback) { Function(int, bool) onSortCallback) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context); final store = StoreProvider.of<AppState>(context);
final company = store.state.company; final company = store.state.company;
@ -680,22 +690,23 @@ class ReportResult {
label: Container( label: Container(
constraints: BoxConstraints(minWidth: 80), constraints: BoxConstraints(minWidth: 80),
child: Text( child: Text(
(company.getCustomFieldLabel(column).isNotEmpty (company
? company.getCustomFieldLabel(column) .getCustomFieldLabel(column)
: localization.lookup(column)) + .isNotEmpty
? company.getCustomFieldLabel(column)
: localization.lookup(column)) +
' ', ' ',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
numeric: numeric:
getReportColumnType(column, context) == ReportColumnType.number, getReportColumnType(column, context) == ReportColumnType.number,
onSort: onSortCallback, onSort: onSortCallback,
) )
]; ];
} }
DataRow tableFilters( DataRow tableFilters(BuildContext context,
BuildContext context,
Map<String, TextEditingController> textEditingControllers, Map<String, TextEditingController> textEditingControllers,
Function(String, String) onFilterChanged) { Function(String, String) onFilterChanged) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
@ -707,124 +718,165 @@ class ReportResult {
if (textEditingControllers == null || if (textEditingControllers == null ||
!textEditingControllers.containsKey(column)) !textEditingControllers.containsKey(column))
DataCell(Text(textEditingControllers == null ? 'null' : 'test')) DataCell(Text(textEditingControllers == null ? 'null' : 'test'))
else if (getReportColumnType(column, context) == ReportColumnType.bool) else
DataCell(AppDropdownButton<bool>( if (getReportColumnType(column, context) == ReportColumnType.bool)
labelText: null, DataCell(AppDropdownButton<bool>(
showBlank: true, labelText: null,
blankValue: null, showBlank: true,
value: textEditingControllers[column].text == 'true' blankValue: null,
? true value: textEditingControllers[column].text == 'true'
: textEditingControllers[column].text == 'false' ? false : null, ? true
onChanged: (dynamic value) { : textEditingControllers[column].text == 'false'
if (value == null) { ? false
textEditingControllers[column].text = ''; : null,
onFilterChanged(column, ''); onChanged: (dynamic value) {
} else { if (value == null) {
textEditingControllers[column].text = value.toString(); textEditingControllers[column].text = '';
onFilterChanged(column, value.toString()); onFilterChanged(column, '');
} } else {
}, textEditingControllers[column].text = value.toString();
items: [ onFilterChanged(column, value.toString());
DropdownMenuItem( }
child: Text(AppLocalization.of(context).yes), },
value: true, items: [
), DropdownMenuItem(
DropdownMenuItem( child: Text(AppLocalization
child: Text(AppLocalization.of(context).no), .of(context)
value: false, .yes),
), value: true,
], ),
)) DropdownMenuItem(
else if (getReportColumnType(column, context) == child: Text(AppLocalization
ReportColumnType.number) .of(context)
DataCell(TextFormField( .no),
controller: textEditingControllers[column], value: false,
keyboardType: TextInputType.numberWithOptions(decimal: true), ),
decoration: InputDecoration( ],
suffixIcon: textEditingControllers == null ))
? null else
: (textEditingControllers[column]?.text ?? '').isEmpty if (getReportColumnType(column, context) ==
ReportColumnType.number)
DataCell(TextFormField(
controller: textEditingControllers[column],
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
suffixIcon: textEditingControllers == null
? null
: (textEditingControllers[column]?.text ?? '').isEmpty
? null ? null
: IconButton( : IconButton(
icon: Icon( icon: Icon(
Icons.clear, Icons.clear,
color: Colors.grey, color: Colors.grey,
), ),
onPressed: () { onPressed: () {
textEditingControllers[column].text = ''; textEditingControllers[column].text = '';
onFilterChanged(column, ''); onFilterChanged(column, '');
}, },
)), )),
)) ))
else if (getReportColumnType(column, context) == else
ReportColumnType.dateTime || if (getReportColumnType(column, context) ==
getReportColumnType(column, context) == ReportColumnType.date) ReportColumnType.dateTime ||
DataCell(AppDropdownButton<DateRange>( getReportColumnType(column, context) == ReportColumnType.date)
labelText: null, DataCell(AppDropdownButton<DateRange>(
showBlank: true, labelText: null,
blankValue: null, showBlank: true,
value: (textEditingControllers[column].text ?? '').isNotEmpty && blankValue: null,
textEditingControllers[column].text != 'null' value: (textEditingControllers[column].text ?? '')
? DateRange.valueOf(textEditingControllers[column].text) .isNotEmpty &&
: null, textEditingControllers[column].text != 'null'
onChanged: (dynamic value) { ? DateRange.valueOf(textEditingControllers[column].text)
if (value == null) { : null,
textEditingControllers[column].text = ''; onChanged: (dynamic value) {
onFilterChanged(column, ''); if (value == null) {
} else { textEditingControllers[column].text = '';
textEditingControllers[column].text = value.toString(); onFilterChanged(column, '');
onFilterChanged(column, value.toString()); } else {
} textEditingControllers[column].text = value.toString();
}, onFilterChanged(column, value.toString());
items: DateRange.values }
.map((dateRange) => DropdownMenuItem<DateRange>( },
child: Text(localization.lookup(dateRange.toString())), items: DateRange.values
value: dateRange, .map((dateRange) =>
)) DropdownMenuItem<DateRange>(
.toList(), child: Text(localization.lookup(dateRange.toString())),
)) value: dateRange,
else ))
DataCell(TypeAheadFormField( .toList(),
noItemsFoundBuilder: (context) => SizedBox(), ))
suggestionsBoxDecoration: SuggestionsBoxDecoration( // TODO remove DEMO_MODE check
constraints: BoxConstraints( else
minWidth: 300, if (Config.DEMO_MODE)
), DataCell(TextFormField(
), controller: textEditingControllers != null
suggestionsCallback: (filter) { ? textEditingControllers[column]
filter = filter.toLowerCase(); : null,
final index = columns.indexOf(column); decoration: InputDecoration(
return data suffixIcon: textEditingControllers == null
.where((row) => ? null
row[index] : (textEditingControllers[column]?.text ?? '')
.renderText(context, column) .isEmpty
.toLowerCase() ? null
.contains(filter) && : IconButton(
row[index].renderText(context, column).trim().isNotEmpty) icon: Icon(
.map((row) => row[index].renderText(context, column)) Icons.clear,
.toSet() color: Colors.grey,
.toList(); ),
}, onPressed: () {
itemBuilder: (context, String entityId) { textEditingControllers[column].text = '';
return Padding( onFilterChanged(column, '');
padding: const EdgeInsets.all(12), },
child: Text('$entityId'), )),
); ))
}, else
onSuggestionSelected: (String value) { DataCell(
textEditingControllers[column].text = value; TypeAheadFormField(
onFilterChanged(column, value); noItemsFoundBuilder: (context) => SizedBox(),
}, suggestionsBoxDecoration: SuggestionsBoxDecoration(
textFieldConfiguration: TextFieldConfiguration<String>( constraints: BoxConstraints(
controller: textEditingControllers != null minWidth: 300,
? textEditingControllers[column] ),
: null, ),
decoration: InputDecoration( suggestionsCallback: (filter) {
suffixIcon: textEditingControllers == null filter = filter.toLowerCase();
? null final index = columns.indexOf(column);
: (textEditingControllers[column]?.text ?? '').isEmpty return data
? null .where((row) =>
: IconButton( row[index]
.renderText(context, column)
.toLowerCase()
.contains(filter) &&
row[index]
.renderText(context, column)
.trim()
.isNotEmpty)
.map((row) =>
row[index].renderText(context, column))
.toSet()
.toList();
},
itemBuilder: (context, String entityId) {
return Padding(
padding: const EdgeInsets.all(12),
child: Text('$entityId'),
);
},
onSuggestionSelected: (String value) {
textEditingControllers[column].text = value;
onFilterChanged(column, value);
},
textFieldConfiguration: TextFieldConfiguration<String>(
controller: textEditingControllers != null
? textEditingControllers[column]
: null,
decoration: InputDecoration(
suffixIcon: textEditingControllers == null
? null
: (textEditingControllers[column]?.text ?? '')
.isEmpty
? null
: IconButton(
icon: Icon( icon: Icon(
Icons.clear, Icons.clear,
color: Colors.grey, color: Colors.grey,
@ -834,11 +886,12 @@ class ReportResult {
onFilterChanged(column, ''); onFilterChanged(column, '');
}, },
)), )),
), ),
autoFlipDirection: true, autoFlipDirection: true,
animationStart: 1, animationStart: 1,
debounceDuration: Duration(seconds: 0), debounceDuration: Duration(seconds: 0),
)) ),
)
]); ]);
} }
@ -875,9 +928,11 @@ class ReportResult {
String value = ''; String value = '';
if (column == groupBy) { if (column == groupBy) {
if (group.isEmpty) { if (group.isEmpty) {
value = AppLocalization.of(context).blank; value = AppLocalization
.of(context)
.blank;
} else if (getReportColumnType(column, context) == } else if (getReportColumnType(column, context) ==
ReportColumnType.dateTime || ReportColumnType.dateTime ||
getReportColumnType(column, context) == ReportColumnType.date) { getReportColumnType(column, context) == ReportColumnType.date) {
value = formatDate(group, context); value = formatDate(group, context);
} else { } else {
@ -897,7 +952,7 @@ class ReportResult {
String customStartDate = ''; String customStartDate = '';
String customEndDate = ''; String customEndDate = '';
if (getReportColumnType(column, context) == if (getReportColumnType(column, context) ==
ReportColumnType.dateTime || ReportColumnType.dateTime ||
getReportColumnType(column, context) == ReportColumnType.date) { getReportColumnType(column, context) == ReportColumnType.date) {
filter = DateRange.custom.toString(); filter = DateRange.custom.toString();
final date = DateTime.tryParse(group); final date = DateTime.tryParse(group);
@ -911,9 +966,13 @@ class ReportResult {
} }
} else if (getReportColumnType(column, context) == } else if (getReportColumnType(column, context) ==
ReportColumnType.bool) { ReportColumnType.bool) {
filter = filter == AppLocalization.of(context).yes filter = filter == AppLocalization
.of(context)
.yes
? 'true' ? 'true'
: filter == AppLocalization.of(context).no ? 'false' : ''; : filter == AppLocalization
.of(context)
.no ? 'false' : '';
} }
store.dispatch( store.dispatch(
UpdateReportSettings( UpdateReportSettings(
@ -933,8 +992,8 @@ class ReportResult {
} }
} }
List<DataColumn> totalColumns( List<DataColumn> totalColumns(BuildContext context,
BuildContext context, Function(int, bool) onSortCallback) { Function(int, bool) onSortCallback) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
columns.toList().sort((String str1, String str2) => str1.compareTo(str2)); columns.toList().sort((String str1, String str2) => str1.compareTo(str2));
@ -965,7 +1024,7 @@ class ReportResult {
final store = StoreProvider.of<AppState>(context); final store = StoreProvider.of<AppState>(context);
final state = store.state; final state = store.state;
final reportSettings = state.userCompany.settings final reportSettings = state.userCompany.settings
?.reportSettings[state.uiState.reportsUIState.report] ?? ?.reportSettings[state.uiState.reportsUIState.report] ??
ReportSettingsEntity(); ReportSettingsEntity();
final Map<String, Map<String, double>> totals = {}; final Map<String, Map<String, double>> totals = {};