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 {
static String productKey = 'productKey';
static String notes = 'notes';
static String cost = 'cost';
static String updatedAt = 'updatedAt';
static String archivedAt = 'archivedAt';
static String isDeleted = 'isDeleted';
static const String productKey = 'productKey';
static const String notes = 'notes';
static const String cost = 'cost';
static const String updatedAt = 'updatedAt';
static const String archivedAt = 'archivedAt';
static const String isDeleted = 'isDeleted';
}
abstract class ProductEntity implements Built<ProductEntity, ProductEntityBuilder> {

View File

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

View File

@ -36,7 +36,7 @@ class InvoiceNinjaApp extends StatelessWidget {
..addAll(createStoreDashboardMiddleware())
..addAll(createStoreProductsMiddleware())
..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/serializer.dart';
import 'package:invoiceninja/ui/app/action_popup_menu.dart';
part 'entity_ui_state.g.dart';
abstract class EntityUIState implements Built<EntityUIState, EntityUIStateBuilder> {
String get sortField;
bool get sortAscending;
BuiltList<int> get stateFilterIds;
BuiltList<int> get statusFilterIds;
factory EntityUIState(sortField) {
return _$EntityUIState._(
sortField: sortField,
sortAscending: true,
stateFilterIds: BuiltList<int>(),
statusFilterIds: BuiltList<int>(),
);
}

View File

@ -30,6 +30,17 @@ class _$EntityUIStateSerializer implements StructuredSerializer<EntityUIState> {
'sortField',
serializers.serialize(object.sortField,
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;
@ -50,6 +61,22 @@ class _$EntityUIStateSerializer implements StructuredSerializer<EntityUIState> {
result.sortField = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
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 {
@override
final String sortField;
@override
final bool sortAscending;
@override
final BuiltList<int> stateFilterIds;
@override
final BuiltList<int> statusFilterIds;
factory _$EntityUIState([void updates(EntityUIStateBuilder b)]) =>
(new EntityUIStateBuilder()..update(updates)).build();
_$EntityUIState._({this.sortField}) : super._() {
_$EntityUIState._(
{this.sortField,
this.sortAscending,
this.stateFilterIds,
this.statusFilterIds})
: super._() {
if (sortField == null)
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
@ -80,18 +124,27 @@ class _$EntityUIState extends EntityUIState {
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! EntityUIState) return false;
return sortField == other.sortField;
return sortField == other.sortField &&
sortAscending == other.sortAscending &&
stateFilterIds == other.stateFilterIds &&
statusFilterIds == other.statusFilterIds;
}
@override
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
String toString() {
return (newBuiltValueToStringHelper('EntityUIState')
..add('sortField', sortField))
..add('sortField', sortField)
..add('sortAscending', sortAscending)
..add('stateFilterIds', stateFilterIds)
..add('statusFilterIds', statusFilterIds))
.toString();
}
}
@ -104,11 +157,31 @@ class EntityUIStateBuilder
String get sortField => _$this._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 get _$this {
if (_$v != null) {
_sortField = _$v.sortField;
_sortAscending = _$v.sortAscending;
_stateFilterIds = _$v.stateFilterIds?.toBuilder();
_statusFilterIds = _$v.statusFilterIds?.toBuilder();
_$v = null;
}
return this;
@ -127,7 +200,27 @@ class EntityUIStateBuilder
@override
_$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);
return _$result;
}

View File

@ -85,4 +85,4 @@ class SaveProductFailure {
class SortProducts {
final String field;
SortProducts(this.field);
}
}

View File

@ -1,4 +1,5 @@
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:invoiceninja/redux/product/product_actions.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/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;
@ -10,21 +13,26 @@ List<ProductEntity> productsSelector(AppState state) =>
state.productState().list.map((id) => state.productState().map[id]);
List<ProductEntity> filteredProductsSelector(
List<ProductEntity> products,
//VisibilityFilter activeFilter,
) {
return products.where((product) {
return true;
/*
if (activeFilter == VisibilityFilter.all) {
return true;
} else if (activeFilter == VisibilityFilter.active) {
return !product.complete;
} else if (activeFilter == VisibilityFilter.completed) {
return product.complete;
ProductState productState,
EntityUIState productUIState) {
var list = productState.list.toList();
list.sort((productAId, productBId) {
var productA = productState.map[productAId];
var productB = productState.map[productBId];
var sortField = productUIState.sortField;
switch (sortField) {
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,
}
class SortField {
final String field;
final String label;
SortField(this.field, this.label);
}
class ActionMenuChoice {
const ActionMenuChoice(this.action, {this.label, this.icon});
final String label;
@ -24,8 +18,8 @@ class ActionMenuButton extends StatelessWidget {
final List<ActionMenuChoice> actions;
final Function onSelected;
final List<SortField> sortFields;
final Function(String) onSelectedSort;
final List<String> sortFields;
final Function(String, Function) onSelectedSort;
final String selectedSort;
ActionMenuButton({
@ -38,30 +32,52 @@ class ActionMenuButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
_showSortScreen() {
print('=== BUILD ===');
showSortScreen() {
scaffoldKey.currentState.showBottomSheet((context) {
return Container(
color: Colors.grey[200],
child: Column(
mainAxisSize: MainAxisSize.min,
children: sortFields.map((sortField) {
print('field: ' + sortField.field );
print('match: ' + (sortField.field == selectedSort ? 'yes' : 'no'));
return RadioListTile(
dense: true,
title: Text(sortField.label),
title: Text(AppLocalization.of((context)).lookup(sortField)),
groupValue: selectedSort,
onChanged: (value) {
print('value changed: ' + value);
this.onSelectedSort(value);
this.onSelectedSort(value, showSortScreen);
},
value: sortField.field,
value: sortField,
);
}).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>(
itemBuilder: (BuildContext context) {
return actions.map((ActionMenuChoice choice) {
@ -91,9 +107,10 @@ class ActionMenuButton extends StatelessWidget {
onSelected: (ActionMenuChoice choice) {
switch (choice.action) {
case ActionMenuButtonType.sort:
_showSortScreen();
showSortScreen();
break;
case ActionMenuButtonType.filter:
showFIlterScree();
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),
child: ListView.builder(
key: NinjaKeys.productList,
itemCount: viewModel.productState.list.length,
itemBuilder: (BuildContext context, int index) {
final product = viewModel.productState.map[viewModel.productState.list[index]];
itemCount: viewModel.products.length,
itemBuilder: (BuildContext context, index) {
var product = viewModel.products[index];
return Column(children: <Widget>[
ProductItem(
product: product,

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:invoiceninja/redux/product/product_selectors.dart';
import 'package:invoiceninja/ui/app/snackbar_row.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
@ -30,14 +31,14 @@ class ProductListBuilder extends StatelessWidget {
}
class ProductListVM {
final ProductState productState;
final List<ProductEntity> products;
final bool isLoading;
final Function(BuildContext, ProductEntity) onProductTap;
final Function(BuildContext, ProductEntity, DismissDirection) onDismissed;
final Function(BuildContext) onRefreshed;
ProductListVM({
@required this.productState,
@required this.products,
@required this.isLoading,
@required this.onProductTap,
@required this.onDismissed,
@ -58,13 +59,7 @@ class ProductListVM {
}
return ProductListVM(
productState: store.state.productState(),
/*
products: filteredProductsSelector(
productsSelector(store.state),
//activeFilterSelector(store.state),
),
*/
products: filteredProductsSelector(store.state.productState(), store.state.productUIState()),
isLoading: store.state.productState().lastUpdated == 0,
onProductTap: (context, 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/ui/app/app_drawer_vm.dart';
import 'package:invoiceninja/ui/app/action_popup_menu.dart';
import 'package:invoiceninja/ui/app/app_bottom_bar.dart';
class ProductScreen extends StatelessWidget {
ProductScreen() : super(key: NinjaKeys.productHome);
final _scaffoldKey = GlobalKey<ScaffoldState>();
//static ActionMenuButtonType _activeSheet;
@override
Widget build(BuildContext context) {
@ -27,34 +29,35 @@ class ProductScreen extends StatelessWidget {
icon: Icon(Icons.search),
onPressed: () {},
),
StoreConnector(
converter: (Store<AppState> store) => store,
builder: (context, store) {
return ActionMenuButton(
scaffoldKey: _scaffoldKey,
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: [
ActionMenuChoice(ActionMenuButtonType.sort),
ActionMenuChoice(ActionMenuButtonType.filter),
],
onSelected: (ActionMenuChoice choice) {},
);
/*
ActionMenuButton(
onSelectedSort: (value, callback) {
StoreProvider.of<AppState>(context).dispatch(SortProducts(value));
//callback();
},
), //FilterSelector(visible: activeTab == AppTab.products),
//ExtraActionsContainer(),
actions: [
ActionMenuChoice(ActionMenuButtonType.sort),
ActionMenuChoice(ActionMenuButtonType.filter),
],
onSelected: (ActionMenuChoice choice) {},
)
*/
],
),
drawer: AppDrawerBuilder(),
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(
converter: (Store<AppState> store) => store,
builder: (context, store) {
@ -67,7 +70,7 @@ class ProductScreen extends StatelessWidget {
MaterialPageRoute(builder: (_) => ProductDetailsBuilder()));
},
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',
'product': 'Product',
'products': 'Products',
'productKey': 'Product Key',
'notes': 'Notes',
'cost': 'Cost',
'clients': 'Clients',
@ -52,6 +53,7 @@ class AppLocalization {
String get newProduct => _localizedValues[locale.languageCode]['new_product'];
String get product => _localizedValues[locale.languageCode]['product'];
String get products => _localizedValues[locale.languageCode]['products'];
String get productKey => _localizedValues[locale.languageCode]['product_key'];
String get notes => _localizedValues[locale.languageCode]['notes'];
String get cost => _localizedValues[locale.languageCode]['cost'];
String get clients => _localizedValues[locale.languageCode]['clients'];
@ -62,7 +64,9 @@ class AppLocalization {
String get successfullyDeletedProduct=> _localizedValues[locale.languageCode]['successfully_deleted_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> {