Sorting..

This commit is contained in:
unknown 2018-05-31 00:24:31 -07:00
parent ef98ff9a4a
commit ecc68040f1
14 changed files with 286 additions and 82 deletions

View File

@ -23,12 +23,12 @@ abstract class ProductItemResponse implements Built<ProductItemResponse, Product
} }
class ProductFields { class ProductFields {
static String productKey = 'productKey'; static const String productKey = 'productKey';
static String notes = 'notes'; static const String notes = 'notes';
static String cost = 'cost'; static const String cost = 'cost';
static String updatedAt = 'updatedAt'; static const String updatedAt = 'updatedAt';
static String archivedAt = 'archivedAt'; static const String archivedAt = 'archivedAt';
static String isDeleted = 'isDeleted'; static const String isDeleted = 'isDeleted';
} }
abstract class ProductEntity implements Built<ProductEntity, ProductEntityBuilder> { abstract class ProductEntity implements Built<ProductEntity, ProductEntityBuilder> {

View File

@ -31,8 +31,6 @@ class WebClient {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
); );
print('== pOST ==');
print(data);
try { try {
final jsonResponse = json.decode(response.body); final jsonResponse = json.decode(response.body);
return jsonResponse; return jsonResponse;

View File

@ -36,7 +36,7 @@ class InvoiceNinjaApp extends StatelessWidget {
..addAll(createStoreDashboardMiddleware()) ..addAll(createStoreDashboardMiddleware())
..addAll(createStoreProductsMiddleware()) ..addAll(createStoreProductsMiddleware())
..addAll([ ..addAll([
LoggingMiddleware.printer(), //LoggingMiddleware.printer(),
]) ])
); );

View File

@ -1,15 +1,23 @@
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart'; import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart'; import 'package:built_value/serializer.dart';
import 'package:invoiceninja/ui/app/action_popup_menu.dart';
part 'entity_ui_state.g.dart'; part 'entity_ui_state.g.dart';
abstract class EntityUIState implements Built<EntityUIState, EntityUIStateBuilder> { abstract class EntityUIState implements Built<EntityUIState, EntityUIStateBuilder> {
String get sortField; String get sortField;
bool get sortAscending;
BuiltList<int> get stateFilterIds;
BuiltList<int> get statusFilterIds;
factory EntityUIState(sortField) { factory EntityUIState(sortField) {
return _$EntityUIState._( return _$EntityUIState._(
sortField: sortField, sortField: sortField,
sortAscending: true,
stateFilterIds: BuiltList<int>(),
statusFilterIds: BuiltList<int>(),
); );
} }

View File

@ -30,6 +30,17 @@ class _$EntityUIStateSerializer implements StructuredSerializer<EntityUIState> {
'sortField', 'sortField',
serializers.serialize(object.sortField, serializers.serialize(object.sortField,
specifiedType: const FullType(String)), specifiedType: const FullType(String)),
'sortAscending',
serializers.serialize(object.sortAscending,
specifiedType: const FullType(bool)),
'stateFilterIds',
serializers.serialize(object.stateFilterIds,
specifiedType:
const FullType(BuiltList, const [const FullType(int)])),
'statusFilterIds',
serializers.serialize(object.statusFilterIds,
specifiedType:
const FullType(BuiltList, const [const FullType(int)])),
]; ];
return result; return result;
@ -50,6 +61,22 @@ class _$EntityUIStateSerializer implements StructuredSerializer<EntityUIState> {
result.sortField = serializers.deserialize(value, result.sortField = serializers.deserialize(value,
specifiedType: const FullType(String)) as String; specifiedType: const FullType(String)) as String;
break; break;
case 'sortAscending':
result.sortAscending = serializers.deserialize(value,
specifiedType: const FullType(bool)) as bool;
break;
case 'stateFilterIds':
result.stateFilterIds.replace(serializers.deserialize(value,
specifiedType:
const FullType(BuiltList, const [const FullType(int)]))
as BuiltList);
break;
case 'statusFilterIds':
result.statusFilterIds.replace(serializers.deserialize(value,
specifiedType:
const FullType(BuiltList, const [const FullType(int)]))
as BuiltList);
break;
} }
} }
@ -60,13 +87,30 @@ class _$EntityUIStateSerializer implements StructuredSerializer<EntityUIState> {
class _$EntityUIState extends EntityUIState { class _$EntityUIState extends EntityUIState {
@override @override
final String sortField; final String sortField;
@override
final bool sortAscending;
@override
final BuiltList<int> stateFilterIds;
@override
final BuiltList<int> statusFilterIds;
factory _$EntityUIState([void updates(EntityUIStateBuilder b)]) => factory _$EntityUIState([void updates(EntityUIStateBuilder b)]) =>
(new EntityUIStateBuilder()..update(updates)).build(); (new EntityUIStateBuilder()..update(updates)).build();
_$EntityUIState._({this.sortField}) : super._() { _$EntityUIState._(
{this.sortField,
this.sortAscending,
this.stateFilterIds,
this.statusFilterIds})
: super._() {
if (sortField == null) if (sortField == null)
throw new BuiltValueNullFieldError('EntityUIState', 'sortField'); throw new BuiltValueNullFieldError('EntityUIState', 'sortField');
if (sortAscending == null)
throw new BuiltValueNullFieldError('EntityUIState', 'sortAscending');
if (stateFilterIds == null)
throw new BuiltValueNullFieldError('EntityUIState', 'stateFilterIds');
if (statusFilterIds == null)
throw new BuiltValueNullFieldError('EntityUIState', 'statusFilterIds');
} }
@override @override
@ -80,18 +124,27 @@ class _$EntityUIState extends EntityUIState {
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (identical(other, this)) return true; if (identical(other, this)) return true;
if (other is! EntityUIState) return false; if (other is! EntityUIState) return false;
return sortField == other.sortField; return sortField == other.sortField &&
sortAscending == other.sortAscending &&
stateFilterIds == other.stateFilterIds &&
statusFilterIds == other.statusFilterIds;
} }
@override @override
int get hashCode { int get hashCode {
return $jf($jc(0, sortField.hashCode)); return $jf($jc(
$jc($jc($jc(0, sortField.hashCode), sortAscending.hashCode),
stateFilterIds.hashCode),
statusFilterIds.hashCode));
} }
@override @override
String toString() { String toString() {
return (newBuiltValueToStringHelper('EntityUIState') return (newBuiltValueToStringHelper('EntityUIState')
..add('sortField', sortField)) ..add('sortField', sortField)
..add('sortAscending', sortAscending)
..add('stateFilterIds', stateFilterIds)
..add('statusFilterIds', statusFilterIds))
.toString(); .toString();
} }
} }
@ -104,11 +157,31 @@ class EntityUIStateBuilder
String get sortField => _$this._sortField; String get sortField => _$this._sortField;
set sortField(String sortField) => _$this._sortField = sortField; set sortField(String sortField) => _$this._sortField = sortField;
bool _sortAscending;
bool get sortAscending => _$this._sortAscending;
set sortAscending(bool sortAscending) =>
_$this._sortAscending = sortAscending;
ListBuilder<int> _stateFilterIds;
ListBuilder<int> get stateFilterIds =>
_$this._stateFilterIds ??= new ListBuilder<int>();
set stateFilterIds(ListBuilder<int> stateFilterIds) =>
_$this._stateFilterIds = stateFilterIds;
ListBuilder<int> _statusFilterIds;
ListBuilder<int> get statusFilterIds =>
_$this._statusFilterIds ??= new ListBuilder<int>();
set statusFilterIds(ListBuilder<int> statusFilterIds) =>
_$this._statusFilterIds = statusFilterIds;
EntityUIStateBuilder(); EntityUIStateBuilder();
EntityUIStateBuilder get _$this { EntityUIStateBuilder get _$this {
if (_$v != null) { if (_$v != null) {
_sortField = _$v.sortField; _sortField = _$v.sortField;
_sortAscending = _$v.sortAscending;
_stateFilterIds = _$v.stateFilterIds?.toBuilder();
_statusFilterIds = _$v.statusFilterIds?.toBuilder();
_$v = null; _$v = null;
} }
return this; return this;
@ -127,7 +200,27 @@ class EntityUIStateBuilder
@override @override
_$EntityUIState build() { _$EntityUIState build() {
final _$result = _$v ?? new _$EntityUIState._(sortField: sortField); _$EntityUIState _$result;
try {
_$result = _$v ??
new _$EntityUIState._(
sortField: sortField,
sortAscending: sortAscending,
stateFilterIds: stateFilterIds.build(),
statusFilterIds: statusFilterIds.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'stateFilterIds';
stateFilterIds.build();
_$failedField = 'statusFilterIds';
statusFilterIds.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'EntityUIState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result); replace(_$result);
return _$result; return _$result;
} }

View File

@ -1,4 +1,5 @@
import 'package:invoiceninja/redux/app/entity_ui_state.dart'; import 'package:invoiceninja/redux/app/entity_ui_state.dart';
import 'package:invoiceninja/ui/app/action_popup_menu.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:invoiceninja/redux/product/product_actions.dart'; import 'package:invoiceninja/redux/product/product_actions.dart';
import 'package:invoiceninja/redux/product/product_state.dart'; import 'package:invoiceninja/redux/product/product_state.dart';

View File

@ -1,5 +1,8 @@
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja/data/models/models.dart'; import 'package:invoiceninja/data/models/models.dart';
import 'package:invoiceninja/redux/app/app_state.dart'; import 'package:invoiceninja/redux/app/app_state.dart';
import 'package:invoiceninja/redux/app/entity_ui_state.dart';
import 'package:invoiceninja/redux/product/product_state.dart';
bool isLoadingSelector(AppState state) => state.isLoading; bool isLoadingSelector(AppState state) => state.isLoading;
@ -10,21 +13,26 @@ List<ProductEntity> productsSelector(AppState state) =>
state.productState().list.map((id) => state.productState().map[id]); state.productState().list.map((id) => state.productState().map[id]);
List<ProductEntity> filteredProductsSelector( List<ProductEntity> filteredProductsSelector(
List<ProductEntity> products, ProductState productState,
//VisibilityFilter activeFilter, EntityUIState productUIState) {
) {
return products.where((product) { var list = productState.list.toList();
return true;
/* list.sort((productAId, productBId) {
if (activeFilter == VisibilityFilter.all) { var productA = productState.map[productAId];
return true; var productB = productState.map[productBId];
} else if (activeFilter == VisibilityFilter.active) { var sortField = productUIState.sortField;
return !product.complete;
} else if (activeFilter == VisibilityFilter.completed) { switch (sortField) {
return product.complete; case ProductFields.productKey:
return productA.productKey.compareTo(productB.productKey);
case ProductFields.cost:
return productA.cost.compareTo(productB.cost);
} }
*/ });
}).toList();
print('== SORTING LIST');
return list.map((id) => productState.map[id]).toList();
} }
/* /*

View File

@ -6,12 +6,6 @@ enum ActionMenuButtonType {
sort, sort,
} }
class SortField {
final String field;
final String label;
SortField(this.field, this.label);
}
class ActionMenuChoice { class ActionMenuChoice {
const ActionMenuChoice(this.action, {this.label, this.icon}); const ActionMenuChoice(this.action, {this.label, this.icon});
final String label; final String label;
@ -24,8 +18,8 @@ class ActionMenuButton extends StatelessWidget {
final List<ActionMenuChoice> actions; final List<ActionMenuChoice> actions;
final Function onSelected; final Function onSelected;
final List<SortField> sortFields; final List<String> sortFields;
final Function(String) onSelectedSort; final Function(String, Function) onSelectedSort;
final String selectedSort; final String selectedSort;
ActionMenuButton({ ActionMenuButton({
@ -38,30 +32,52 @@ class ActionMenuButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_showSortScreen() { print('=== BUILD ===');
showSortScreen() {
scaffoldKey.currentState.showBottomSheet((context) { scaffoldKey.currentState.showBottomSheet((context) {
return Container( return Container(
color: Colors.grey[200], color: Colors.grey[200],
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: sortFields.map((sortField) { children: sortFields.map((sortField) {
print('field: ' + sortField.field );
print('match: ' + (sortField.field == selectedSort ? 'yes' : 'no'));
return RadioListTile( return RadioListTile(
dense: true, dense: true,
title: Text(sortField.label), title: Text(AppLocalization.of((context)).lookup(sortField)),
groupValue: selectedSort, groupValue: selectedSort,
onChanged: (value) { onChanged: (value) {
print('value changed: ' + value); this.onSelectedSort(value, showSortScreen);
this.onSelectedSort(value);
}, },
value: sortField.field, value: sortField,
); );
}).toList()), }).toList()),
); );
}); });
} }
showFIlterScree() {
scaffoldKey.currentState.showBottomSheet((context) {
bool _active = false;
return Container(
color: Colors.grey[200],
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CheckboxListTile(
value: true,
title: Text('Active'),
selected: _active,
onChanged: (checked) {
_active = ! checked;
},
),
]
),
);
});
}
return PopupMenuButton<ActionMenuChoice>( return PopupMenuButton<ActionMenuChoice>(
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return actions.map((ActionMenuChoice choice) { return actions.map((ActionMenuChoice choice) {
@ -91,9 +107,10 @@ class ActionMenuButton extends StatelessWidget {
onSelected: (ActionMenuChoice choice) { onSelected: (ActionMenuChoice choice) {
switch (choice.action) { switch (choice.action) {
case ActionMenuButtonType.sort: case ActionMenuButtonType.sort:
_showSortScreen(); showSortScreen();
break; break;
case ActionMenuButtonType.filter: case ActionMenuButtonType.filter:
showFIlterScree();
break; break;
} }
}, },

View File

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:invoiceninja/utils/localization.dart';
/*
enum BottomBarButtonType {
filter,
sort,
}
*/
/*
class _AppBottomBarChoice {
const _AppBottomBarChoice (this.type, {this.label, this.icon});
final String label;
final IconData icon;
final BottomBarButtonType type;
}
*/
class AppBottomBar extends StatelessWidget {
final GlobalKey<ScaffoldState> scaffoldKey;
//final List<ActionMenuChoice> actions;
final List<String> sortFields;
final Function(String) onSelectedSort;
final String selectedSort;
AppBottomBar(
{
//this.actions,
//this.onSelected,
this.scaffoldKey,
this.sortFields,
this.onSelectedSort,
this.selectedSort});
@override
Widget build(BuildContext context) {
return new BottomAppBar(
hasNotch: true,
child: Row(
children: <Widget>[
IconButton(
tooltip: AppLocalization.of((context)).sort,
icon: Icon(Icons.sort_by_alpha),
onPressed: () {
scaffoldKey.currentState.showBottomSheet((context) {
return Container(
color: Colors.grey[200],
child: Column(
mainAxisSize: MainAxisSize.min,
children: sortFields.map((sortField) {
return RadioListTile(
dense: true,
title: Text(
AppLocalization.of((context)).lookup(sortField)),
groupValue: selectedSort,
onChanged: (value) {
this.onSelectedSort(value);
},
value: sortField,
);
}).toList()),
);
});
},
),
IconButton(
tooltip: AppLocalization.of((context)).filter,
icon: Icon(Icons.filter_list),
onPressed: () {},
),
],
),
);
}
}

View File

@ -24,9 +24,9 @@ class ProductList extends StatelessWidget {
onRefresh: () => viewModel.onRefreshed(context), onRefresh: () => viewModel.onRefreshed(context),
child: ListView.builder( child: ListView.builder(
key: NinjaKeys.productList, key: NinjaKeys.productList,
itemCount: viewModel.productState.list.length, itemCount: viewModel.products.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, index) {
final product = viewModel.productState.map[viewModel.productState.list[index]]; var product = viewModel.products[index];
return Column(children: <Widget>[ return Column(children: <Widget>[
ProductItem( ProductItem(
product: product, product: product,

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:invoiceninja/redux/product/product_selectors.dart';
import 'package:invoiceninja/ui/app/snackbar_row.dart'; import 'package:invoiceninja/ui/app/snackbar_row.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -30,14 +31,14 @@ class ProductListBuilder extends StatelessWidget {
} }
class ProductListVM { class ProductListVM {
final ProductState productState; final List<ProductEntity> products;
final bool isLoading; final bool isLoading;
final Function(BuildContext, ProductEntity) onProductTap; final Function(BuildContext, ProductEntity) onProductTap;
final Function(BuildContext, ProductEntity, DismissDirection) onDismissed; final Function(BuildContext, ProductEntity, DismissDirection) onDismissed;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
ProductListVM({ ProductListVM({
@required this.productState, @required this.products,
@required this.isLoading, @required this.isLoading,
@required this.onProductTap, @required this.onProductTap,
@required this.onDismissed, @required this.onDismissed,
@ -58,13 +59,7 @@ class ProductListVM {
} }
return ProductListVM( return ProductListVM(
productState: store.state.productState(), products: filteredProductsSelector(store.state.productState(), store.state.productUIState()),
/*
products: filteredProductsSelector(
productsSelector(store.state),
//activeFilterSelector(store.state),
),
*/
isLoading: store.state.productState().lastUpdated == 0, isLoading: store.state.productState().lastUpdated == 0,
onProductTap: (context, product) { onProductTap: (context, product) {
store.dispatch(SelectProductAction(product)); store.dispatch(SelectProductAction(product));

View File

@ -10,11 +10,13 @@ import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja/redux/product/product_actions.dart'; import 'package:invoiceninja/redux/product/product_actions.dart';
import 'package:invoiceninja/ui/app/app_drawer_vm.dart'; import 'package:invoiceninja/ui/app/app_drawer_vm.dart';
import 'package:invoiceninja/ui/app/action_popup_menu.dart'; import 'package:invoiceninja/ui/app/action_popup_menu.dart';
import 'package:invoiceninja/ui/app/app_bottom_bar.dart';
class ProductScreen extends StatelessWidget { class ProductScreen extends StatelessWidget {
ProductScreen() : super(key: NinjaKeys.productHome); ProductScreen() : super(key: NinjaKeys.productHome);
final _scaffoldKey = GlobalKey<ScaffoldState>(); final _scaffoldKey = GlobalKey<ScaffoldState>();
//static ActionMenuButtonType _activeSheet;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,34 +29,35 @@ class ProductScreen extends StatelessWidget {
icon: Icon(Icons.search), icon: Icon(Icons.search),
onPressed: () {}, onPressed: () {},
), ),
StoreConnector( /*
converter: (Store<AppState> store) => store, ActionMenuButton(
builder: (context, store) { onSelectedSort: (value, callback) {
return ActionMenuButton( StoreProvider.of<AppState>(context).dispatch(SortProducts(value));
scaffoldKey: _scaffoldKey, //callback();
onSelectedSort: (value) {
store.dispatch(SortProducts(value));
}, },
selectedSort: store.state.productUIState().sortField,
sortFields: [
SortField(ProductFields.productKey,
AppLocalization.of((context)).product),
SortField(
ProductFields.cost, AppLocalization.of((context)).cost),
],
actions: [ actions: [
ActionMenuChoice(ActionMenuButtonType.sort), ActionMenuChoice(ActionMenuButtonType.sort),
ActionMenuChoice(ActionMenuButtonType.filter), ActionMenuChoice(ActionMenuButtonType.filter),
], ],
onSelected: (ActionMenuChoice choice) {}, onSelected: (ActionMenuChoice choice) {},
); )
}, */
), //FilterSelector(visible: activeTab == AppTab.products),
//ExtraActionsContainer(),
], ],
), ),
drawer: AppDrawerBuilder(), drawer: AppDrawerBuilder(),
body: ProductListBuilder(), body: ProductListBuilder(),
bottomNavigationBar: AppBottomBar(
scaffoldKey: _scaffoldKey,
selectedSort: StoreProvider.of<AppState>(context).state.productUIState().sortField,
onSelectedSort: (value) {
StoreProvider.of<AppState>(context).dispatch(SortProducts(value));
},
sortFields: [
ProductFields.productKey,
ProductFields.cost,
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: StoreConnector( floatingActionButton: StoreConnector(
converter: (Store<AppState> store) => store, converter: (Store<AppState> store) => store,
builder: (context, store) { builder: (context, store) {
@ -67,7 +70,7 @@ class ProductScreen extends StatelessWidget {
MaterialPageRoute(builder: (_) => ProductDetailsBuilder())); MaterialPageRoute(builder: (_) => ProductDetailsBuilder()));
}, },
child: Icon(Icons.add), child: Icon(Icons.add),
//tooltip: ArchSampleLocalizations.of(context).addProduct, tooltip: AppLocalization.of(context).newProduct,
); );
}, },
), ),

View File

@ -26,6 +26,7 @@ class AppLocalization {
'new_product': 'New Product', 'new_product': 'New Product',
'product': 'Product', 'product': 'Product',
'products': 'Products', 'products': 'Products',
'productKey': 'Product Key',
'notes': 'Notes', 'notes': 'Notes',
'cost': 'Cost', 'cost': 'Cost',
'clients': 'Clients', 'clients': 'Clients',
@ -52,6 +53,7 @@ class AppLocalization {
String get newProduct => _localizedValues[locale.languageCode]['new_product']; String get newProduct => _localizedValues[locale.languageCode]['new_product'];
String get product => _localizedValues[locale.languageCode]['product']; String get product => _localizedValues[locale.languageCode]['product'];
String get products => _localizedValues[locale.languageCode]['products']; String get products => _localizedValues[locale.languageCode]['products'];
String get productKey => _localizedValues[locale.languageCode]['product_key'];
String get notes => _localizedValues[locale.languageCode]['notes']; String get notes => _localizedValues[locale.languageCode]['notes'];
String get cost => _localizedValues[locale.languageCode]['cost']; String get cost => _localizedValues[locale.languageCode]['cost'];
String get clients => _localizedValues[locale.languageCode]['clients']; String get clients => _localizedValues[locale.languageCode]['clients'];
@ -62,7 +64,9 @@ class AppLocalization {
String get successfullyDeletedProduct=> _localizedValues[locale.languageCode]['successfully_deleted_product']; String get successfullyDeletedProduct=> _localizedValues[locale.languageCode]['successfully_deleted_product'];
String get successfullyRestoredProduct => _localizedValues[locale.languageCode]['successfully_restored_product']; String get successfullyRestoredProduct => _localizedValues[locale.languageCode]['successfully_restored_product'];
String lookup(String key) {
return _localizedValues[locale.languageCode][key] ?? 'Missing: ' + key;
}
} }
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalization> { class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalization> {