diff --git a/lib/data/models/serializers.g.dart b/lib/data/models/serializers.g.dart index 097facbee..dade93bc3 100644 --- a/lib/data/models/serializers.g.dart +++ b/lib/data/models/serializers.g.dart @@ -242,6 +242,12 @@ Serializers _$serializers = (new Serializers().toBuilder() ..addBuilderFactory( const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(String)]), + () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(String)]), + () => new ListBuilder()) ..addBuilderFactory( const FullType(BuiltList, const [const FullType(ExpenseEntity)]), () => new ListBuilder()) @@ -276,10 +282,8 @@ Serializers _$serializers = (new Serializers().toBuilder() BuiltList, const [const FullType(ExpenseCategoryEntity)]), () => new ListBuilder()) ..addBuilderFactory( - const FullType(BuiltMap, const [ - const FullType(String), - const FullType(ExpenseCategoryEntity) - ]), + const FullType(BuiltMap, + const [const FullType(String), const FullType(ExpenseCategoryEntity)]), () => new MapBuilder()) ..addBuilderFactory( const FullType(BuiltList, const [const FullType(UserEntity)]), @@ -327,8 +331,7 @@ Serializers _$serializers = (new Serializers().toBuilder() const FullType(BuiltList, const [const FullType(ProjectEntity)]), () => new ListBuilder()) ..addBuilderFactory( - const FullType(BuiltList, const [const FullType(SizeEntity)]), - () => new ListBuilder()) + const FullType(BuiltList, const [const FullType(SizeEntity)]), () => new ListBuilder()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(TaskEntity)]), () => new ListBuilder()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(TaxRateEntity)]), () => new ListBuilder()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(TimezoneEntity)]), () => new ListBuilder()) diff --git a/lib/redux/client/client_actions.dart b/lib/redux/client/client_actions.dart index 830becf2e..3c1ed942a 100644 --- a/lib/redux/client/client_actions.dart +++ b/lib/redux/client/client_actions.dart @@ -268,6 +268,18 @@ class FilterClientsByCustom2 implements PersistUI { final String value; } +class FilterClientsByCustom3 implements PersistUI { + FilterClientsByCustom3(this.value); + + final String value; +} + +class FilterClientsByCustom4 implements PersistUI { + FilterClientsByCustom4(this.value); + + final String value; +} + void handleClientAction( BuildContext context, List clients, EntityAction action) { assert( diff --git a/lib/redux/client/client_reducer.dart b/lib/redux/client/client_reducer.dart index 8e8baf9d0..23a14f472 100644 --- a/lib/redux/client/client_reducer.dart +++ b/lib/redux/client/client_reducer.dart @@ -106,6 +106,8 @@ final clientListReducer = combineReducers([ TypedReducer(_filterClientsByEntity), TypedReducer(_filterClientsByCustom1), TypedReducer(_filterClientsByCustom2), + TypedReducer(_filterClientsByCustom3), + TypedReducer(_filterClientsByCustom4), TypedReducer(_startListMultiselect), TypedReducer(_addToListMultiselect), TypedReducer( @@ -133,6 +135,26 @@ ListUIState _filterClientsByCustom2( } } +ListUIState _filterClientsByCustom3( + ListUIState clientListState, FilterClientsByCustom3 action) { + if (clientListState.custom3Filters.contains(action.value)) { + return clientListState + .rebuild((b) => b..custom3Filters.remove(action.value)); + } else { + return clientListState.rebuild((b) => b..custom3Filters.add(action.value)); + } +} + +ListUIState _filterClientsByCustom4( + ListUIState clientListState, FilterClientsByCustom4 action) { + if (clientListState.custom4Filters.contains(action.value)) { + return clientListState + .rebuild((b) => b..custom4Filters.remove(action.value)); + } else { + return clientListState.rebuild((b) => b..custom4Filters.add(action.value)); + } +} + ListUIState _filterClientsByState( ListUIState clientListState, FilterClientsByState action) { if (clientListState.stateFilters.contains(action.state)) { diff --git a/lib/redux/ui/list_ui_state.dart b/lib/redux/ui/list_ui_state.dart index 35dd69ac4..48d47370e 100644 --- a/lib/redux/ui/list_ui_state.dart +++ b/lib/redux/ui/list_ui_state.dart @@ -17,6 +17,8 @@ abstract class ListUIState implements Built { statusFilters: BuiltList(), custom1Filters: BuiltList(), custom2Filters: BuiltList(), + custom3Filters: BuiltList(), + custom4Filters: BuiltList(), filter: null); } @@ -49,6 +51,10 @@ abstract class ListUIState implements Built { BuiltList get custom2Filters; + BuiltList get custom3Filters; + + BuiltList get custom4Filters; + bool get hasStateFilters => stateFilters.length != 1 || stateFilters.first != EntityState.active; @@ -58,6 +64,10 @@ abstract class ListUIState implements Built { bool get hasCustom2Filters => custom2Filters.isNotEmpty; + bool get hasCustom3Filters => custom3Filters.isNotEmpty; + + bool get hasCustom4Filters => custom4Filters.isNotEmpty; + @nullable BuiltList get selectedIds; diff --git a/lib/redux/ui/list_ui_state.g.dart b/lib/redux/ui/list_ui_state.g.dart index 590399aaf..fdc20320a 100644 --- a/lib/redux/ui/list_ui_state.g.dart +++ b/lib/redux/ui/list_ui_state.g.dart @@ -43,6 +43,14 @@ class _$ListUIStateSerializer implements StructuredSerializer { serializers.serialize(object.custom2Filters, specifiedType: const FullType(BuiltList, const [const FullType(String)])), + 'custom3Filters', + serializers.serialize(object.custom3Filters, + specifiedType: + const FullType(BuiltList, const [const FullType(String)])), + 'custom4Filters', + serializers.serialize(object.custom4Filters, + specifiedType: + const FullType(BuiltList, const [const FullType(String)])), ]; if (object.filter != null) { result @@ -131,6 +139,18 @@ class _$ListUIStateSerializer implements StructuredSerializer { const FullType(BuiltList, const [const FullType(String)])) as BuiltList); break; + case 'custom3Filters': + result.custom3Filters.replace(serializers.deserialize(value, + specifiedType: + const FullType(BuiltList, const [const FullType(String)])) + as BuiltList); + break; + case 'custom4Filters': + result.custom4Filters.replace(serializers.deserialize(value, + specifiedType: + const FullType(BuiltList, const [const FullType(String)])) + as BuiltList); + break; case 'selectedIds': result.selectedIds.replace(serializers.deserialize(value, specifiedType: @@ -166,6 +186,10 @@ class _$ListUIState extends ListUIState { @override final BuiltList custom2Filters; @override + final BuiltList custom3Filters; + @override + final BuiltList custom4Filters; + @override final BuiltList selectedIds; factory _$ListUIState([void Function(ListUIStateBuilder) updates]) => @@ -182,6 +206,8 @@ class _$ListUIState extends ListUIState { this.statusFilters, this.custom1Filters, this.custom2Filters, + this.custom3Filters, + this.custom4Filters, this.selectedIds}) : super._() { if (filterClearedAt == null) { @@ -205,6 +231,12 @@ class _$ListUIState extends ListUIState { if (custom2Filters == null) { throw new BuiltValueNullFieldError('ListUIState', 'custom2Filters'); } + if (custom3Filters == null) { + throw new BuiltValueNullFieldError('ListUIState', 'custom3Filters'); + } + if (custom4Filters == null) { + throw new BuiltValueNullFieldError('ListUIState', 'custom4Filters'); + } } @override @@ -228,6 +260,8 @@ class _$ListUIState extends ListUIState { statusFilters == other.statusFilters && custom1Filters == other.custom1Filters && custom2Filters == other.custom2Filters && + custom3Filters == other.custom3Filters && + custom4Filters == other.custom4Filters && selectedIds == other.selectedIds; } @@ -242,16 +276,20 @@ class _$ListUIState extends ListUIState { $jc( $jc( $jc( - $jc($jc(0, filter.hashCode), - filterClearedAt.hashCode), - filterEntityId.hashCode), - filterEntityType.hashCode), - sortField.hashCode), - sortAscending.hashCode), - stateFilters.hashCode), - statusFilters.hashCode), - custom1Filters.hashCode), - custom2Filters.hashCode), + $jc( + $jc( + $jc($jc(0, filter.hashCode), + filterClearedAt.hashCode), + filterEntityId.hashCode), + filterEntityType.hashCode), + sortField.hashCode), + sortAscending.hashCode), + stateFilters.hashCode), + statusFilters.hashCode), + custom1Filters.hashCode), + custom2Filters.hashCode), + custom3Filters.hashCode), + custom4Filters.hashCode), selectedIds.hashCode)); } @@ -268,6 +306,8 @@ class _$ListUIState extends ListUIState { ..add('statusFilters', statusFilters) ..add('custom1Filters', custom1Filters) ..add('custom2Filters', custom2Filters) + ..add('custom3Filters', custom3Filters) + ..add('custom4Filters', custom4Filters) ..add('selectedIds', selectedIds)) .toString(); } @@ -328,6 +368,18 @@ class ListUIStateBuilder implements Builder { set custom2Filters(ListBuilder custom2Filters) => _$this._custom2Filters = custom2Filters; + ListBuilder _custom3Filters; + ListBuilder get custom3Filters => + _$this._custom3Filters ??= new ListBuilder(); + set custom3Filters(ListBuilder custom3Filters) => + _$this._custom3Filters = custom3Filters; + + ListBuilder _custom4Filters; + ListBuilder get custom4Filters => + _$this._custom4Filters ??= new ListBuilder(); + set custom4Filters(ListBuilder custom4Filters) => + _$this._custom4Filters = custom4Filters; + ListBuilder _selectedIds; ListBuilder get selectedIds => _$this._selectedIds ??= new ListBuilder(); @@ -348,6 +400,8 @@ class ListUIStateBuilder implements Builder { _statusFilters = _$v.statusFilters?.toBuilder(); _custom1Filters = _$v.custom1Filters?.toBuilder(); _custom2Filters = _$v.custom2Filters?.toBuilder(); + _custom3Filters = _$v.custom3Filters?.toBuilder(); + _custom4Filters = _$v.custom4Filters?.toBuilder(); _selectedIds = _$v.selectedIds?.toBuilder(); _$v = null; } @@ -383,6 +437,8 @@ class ListUIStateBuilder implements Builder { statusFilters: statusFilters.build(), custom1Filters: custom1Filters.build(), custom2Filters: custom2Filters.build(), + custom3Filters: custom3Filters.build(), + custom4Filters: custom4Filters.build(), selectedIds: _selectedIds?.build()); } catch (_) { String _$failedField; @@ -395,6 +451,10 @@ class ListUIStateBuilder implements Builder { custom1Filters.build(); _$failedField = 'custom2Filters'; custom2Filters.build(); + _$failedField = 'custom3Filters'; + custom3Filters.build(); + _$failedField = 'custom4Filters'; + custom4Filters.build(); _$failedField = 'selectedIds'; _selectedIds?.build(); } catch (e) { diff --git a/lib/ui/app/app_bottom_bar.dart b/lib/ui/app/app_bottom_bar.dart index 2187e6318..5bb5605f0 100644 --- a/lib/ui/app/app_bottom_bar.dart +++ b/lib/ui/app/app_bottom_bar.dart @@ -16,11 +16,15 @@ class AppBottomBar extends StatefulWidget { this.entityType, this.onSelectedState, this.onSelectedStatus, - this.onSelectedCustom1, - this.onSelectedCustom2, + @required this.onSelectedCustom1, + @required this.onSelectedCustom2, + @required this.onSelectedCustom3, + @required this.onSelectedCustom4, this.statuses = const [], - this.customValues1 = const [], - this.customValues2 = const [], + @required this.customValues1 = const [], + @required this.customValues2 = const [], + @required this.customValues3 = const [], + @required this.customValues4 = const [], }); final EntityType entityType; @@ -31,8 +35,12 @@ class AppBottomBar extends StatefulWidget { final Function(EntityStatus, bool) onSelectedStatus; final Function(String) onSelectedCustom1; final Function(String) onSelectedCustom2; + final Function(String) onSelectedCustom3; + final Function(String) onSelectedCustom4; final List customValues1; final List customValues2; + final List customValues3; + final List customValues4; @override _AppBottomBarState createState() => _AppBottomBarState(); @@ -44,6 +52,8 @@ class _AppBottomBarState extends State { PersistentBottomSheetController _filterStatusController; PersistentBottomSheetController _filterCustom1Controller; PersistentBottomSheetController _filterCustom2Controller; + PersistentBottomSheetController _filterCustom3Controller; + PersistentBottomSheetController _filterCustom4Controller; @override Widget build(BuildContext context) { @@ -260,6 +270,88 @@ class _AppBottomBarState extends State { }); }; + final _showFilterCustom3Sheet = () { + if (_filterCustom3Controller != null) { + _filterCustom3Controller.close(); + return; + } + + _filterCustom3Controller = + Scaffold.of(context).showBottomSheet((context) { + return StoreConnector>( + converter: (Store store) => + store.state.getListState(widget.entityType).custom1Filters, + builder: (BuildContext context, customFilters) { + return Container( + color: Theme.of(context).backgroundColor, + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Column( + children: widget.customValues3.map((customField) { + return CheckboxListTile( + key: Key(customField.toString()), + title: Text(customField), + controlAffinity: ListTileControlAffinity.leading, + value: customFilters.contains(customField), + activeColor: Theme.of(context).accentColor, + dense: true, + onChanged: (value) { + widget.onSelectedCustom3(customField); + }, + ); + }).toList(), + ), + ]), + ); + }, + ); + }); + + _filterCustom3Controller.closed.whenComplete(() { + _filterCustom3Controller = null; + }); + }; + + final _showFilterCustom4Sheet = () { + if (_filterCustom4Controller != null) { + _filterCustom4Controller.close(); + return; + } + + _filterCustom4Controller = + Scaffold.of(context).showBottomSheet((context) { + return StoreConnector>( + converter: (Store store) => + store.state.getListState(widget.entityType).custom4Filters, + builder: (BuildContext context, customFilters) { + return Container( + color: Theme.of(context).backgroundColor, + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Column( + children: widget.customValues4.map((customField) { + return CheckboxListTile( + key: Key(customField.toString()), + title: Text(customField), + controlAffinity: ListTileControlAffinity.leading, + value: customFilters.contains(customField), + activeColor: Theme.of(context).accentColor, + dense: true, + onChanged: (value) { + widget.onSelectedCustom4(customField); + }, + ); + }).toList(), + ), + ]), + ); + }, + ); + }); + + _filterCustom4Controller.closed.whenComplete(() { + _filterCustom4Controller = null; + }); + }; + return StoreBuilder(builder: (BuildContext context, Store store) { final localization = AppLocalization.of(context); final prefState = store.state.prefState; @@ -318,8 +410,30 @@ class _AppBottomBarState extends State { icon: Icon(Icons.looks_two), onPressed: _showFilterCustom2Sheet, color: store.state - .getListState(widget.entityType) - .hasCustom2Filters + .getListState(widget.entityType) + .hasCustom2Filters + ? Theme.of(context).accentColor + : null, + ), + if (widget.customValues3.isNotEmpty) + IconButton( + tooltip: AppLocalization.of(context).filter, + icon: Icon(Icons.looks_two), + onPressed: _showFilterCustom3Sheet, + color: store.state + .getListState(widget.entityType) + .hasCustom3Filters + ? Theme.of(context).accentColor + : null, + ), + if (widget.customValues4.isNotEmpty) + IconButton( + tooltip: AppLocalization.of(context).filter, + icon: Icon(Icons.looks_two), + onPressed: _showFilterCustom4Sheet, + color: store.state + .getListState(widget.entityType) + .hasCustom4Filters ? Theme.of(context).accentColor : null, ), diff --git a/lib/ui/client/client_screen.dart b/lib/ui/client/client_screen.dart index ac2ca08ae..ed80a21df 100644 --- a/lib/ui/client/client_screen.dart +++ b/lib/ui/client/client_screen.dart @@ -70,16 +70,16 @@ class ClientScreen extends StatelessWidget { onSavePressed: state.clientListState.selectedIds.isEmpty ? null : (context) async { - final clients = viewModel.clientList - .map( - (clientId) => viewModel.clientMap[clientId]) - .toList(); + final clients = viewModel.clientList + .map( + (clientId) => viewModel.clientMap[clientId]) + .toList(); - await showEntityActionsDialog( - entities: clients, context: context, multiselect: true); + await showEntityActionsDialog( + entities: clients, context: context, multiselect: true); - store.dispatch(ClearClientMultiselect()); - }, + store.dispatch(ClearClientMultiselect()); + }, onCancelPressed: (context) => store.dispatch(ClearClientMultiselect()), ), @@ -102,25 +102,35 @@ class ClientScreen extends StatelessWidget { excludeBlank: true), customValues2: company.getCustomFieldValues(CustomFieldType.client2, excludeBlank: true), + customValues3: company.getCustomFieldValues(CustomFieldType.client3, + excludeBlank: true), + customValues4: company.getCustomFieldValues(CustomFieldType.client4, + excludeBlank: true), onSelectedCustom1: (value) => store.dispatch(FilterClientsByCustom1(value)), onSelectedCustom2: (value) => store.dispatch(FilterClientsByCustom2(value)), + onSelectedCustom3: (value) => + store.dispatch(FilterClientsByCustom3(value)), + onSelectedCustom4: (value) => + store.dispatch(FilterClientsByCustom4(value)), ), floatingActionButton: userCompany.canCreate(EntityType.client) ? FloatingActionButton( - heroTag: 'client_fab', - backgroundColor: Theme.of(context).primaryColorDark, - onPressed: () { - createEntityByType( - context: context, entityType: EntityType.client); - }, - child: Icon( - Icons.add, - color: Colors.white, - ), - tooltip: localization.newClient, - ) + heroTag: 'client_fab', + backgroundColor: Theme + .of(context) + .primaryColorDark, + onPressed: () { + createEntityByType( + context: context, entityType: EntityType.client); + }, + child: Icon( + Icons.add, + color: Colors.white, + ), + tooltip: localization.newClient, + ) : null, ); }