diff --git a/lib/ui/project/project_list.dart b/lib/ui/project/project_list.dart index 85587e750..9d1dd9a69 100644 --- a/lib/ui/project/project_list.dart +++ b/lib/ui/project/project_list.dart @@ -14,30 +14,11 @@ class ProjectList extends StatelessWidget { @required this.viewModel, }) : super(key: key); - @override - Widget build(BuildContext context) { - if (!viewModel.isLoaded) { - return LoadingIndicator(); - } else if (viewModel.projectList.isEmpty) { - return Opacity( - opacity: 0.5, - child: Center( - child: Text( - AppLocalization.of(context).noRecordsFound, - style: TextStyle( - fontSize: 18.0, - ), - ), - ), - ); - } - - return _buildListView(context); - } + final ProjectListVM viewModel; void _showMenu( - BuildContext context, ProjectEntity project) async { - if (project == null) { + BuildContext context, ProjectEntity project, ClientEntity client) async { + if (project == null || client == null) { return; } @@ -45,9 +26,9 @@ class ProjectList extends StatelessWidget { final message = await showDialog( context: context, builder: (BuildContext dialogContext) => SimpleDialog( - children: project - .getEntityActions(user: user) - .map((entityAction) { + children: project + .getEntityActions(user: user, client: client) + .map((entityAction) { if (entityAction == null) { return Divider(); } else { @@ -66,42 +47,109 @@ class ProjectList extends StatelessWidget { if (message != null) { Scaffold.of(context).showSnackBar(SnackBar( content: SnackBarRow( - message: message, - ))); + message: message, + ))); } } - Widget _buildListView(BuildContext context) { - return RefreshIndicator( - onRefresh: () => viewModel.onRefreshed(context), - child: ListView.builder( - itemCount: viewModel.projectList.length, - itemBuilder: (BuildContext context, index) { - final projectId = viewModel.projectList[index]; - final project = viewModel.projectMap[projectId]; - return Column(children: [ - ProjectListItem( - user: viewModel.user, - filter: viewModel.filter, - project: project, - client: viewModel.clientMap[project.clientId], - onEntityAction: (EntityAction action) { - if (action == EntityAction.more) { - _showMenu(context, project); - } else { - viewModel.onEntityAction(context, project, action); - } - }, - onTap: () => viewModel.onProjectTap(context, project), - onLongPress: () => _showMenu(context, project), + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + final listState = viewModel.listState; + final filteredClientId = listState.filterEntityId; + final filteredClient = + filteredClientId != null ? viewModel.clientMap[filteredClientId] : null; + + return Column( + children: [ + filteredClient != null + ? Material( + color: Colors.orangeAccent, + elevation: 6.0, + child: InkWell( + onTap: () => viewModel.onViewEntityFilterPressed(context), + child: Row( + children: [ + SizedBox(width: 18.0), + Expanded( + child: Text( + '${localization.filteredBy} ${filteredClient.listDisplayName}', + style: TextStyle( + color: Colors.white, + fontSize: 16.0, + ), + ), + ), + IconButton( + icon: Icon( + Icons.close, + color: Colors.white, + ), + onPressed: () => viewModel.onClearEntityFilterPressed(), + ) + ], + ), + ), + ) + : Container(), + Expanded( + child: !viewModel.isLoaded + ? LoadingIndicator() + : RefreshIndicator( + onRefresh: () => viewModel.onRefreshed(context), + child: viewModel.projectList.isEmpty + ? Opacity( + opacity: 0.5, + child: Center( + child: Text( + AppLocalization.of(context).noRecordsFound, + style: TextStyle( + fontSize: 18.0, + ), + ), ), - Divider( - height: 1.0, - ), - ]); - }), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: viewModel.projectList.length, + itemBuilder: (BuildContext context, index) { + final projectId = viewModel.projectList[index]; + final project = viewModel.projectMap[projectId]; + final client = + viewModel.clientMap[project.clientId] ?? + ClientEntity(); + return Column( + children: [ + ProjectListItem( + user: viewModel.user, + filter: viewModel.filter, + project: project, + client: + viewModel.clientMap[project.clientId] ?? + ClientEntity(), + onTap: () => + viewModel.onProjectTap(context, project), + onEntityAction: (EntityAction action) { + if (action == EntityAction.more) { + _showMenu(context, project, client); + } else { + viewModel.onEntityAction( + context, project, action); + } + }, + onLongPress: () => + _showMenu(context, project, client), + ), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + ), + ], ); } - - final ProjectListVM viewModel; } diff --git a/lib/ui/project/project_list_vm.dart b/lib/ui/project/project_list_vm.dart index 80e69ccc5..65b22e8ea 100644 --- a/lib/ui/project/project_list_vm.dart +++ b/lib/ui/project/project_list_vm.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; +import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; import 'package:redux/redux.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; @@ -35,12 +37,15 @@ class ProjectListVM { @required this.projectList, @required this.projectMap, @required this.clientMap, + @required this.listState, @required this.filter, @required this.isLoading, @required this.isLoaded, @required this.onProjectTap, @required this.onRefreshed, @required this.onEntityAction, + @required this.onClearEntityFilterPressed, + @required this.onViewEntityFilterPressed, }); static ProjectListVM fromStore(Store store) { @@ -58,6 +63,7 @@ class ProjectListVM { return ProjectListVM( user: state.user, + listState: state.projectListState, projectList: memoizedFilteredProjectList(state.projectState.map, state.projectState.list, state.projectListState, state.clientState.map), projectMap: state.projectState.map, @@ -65,6 +71,12 @@ class ProjectListVM { isLoading: state.isLoading, isLoaded: state.projectState.isLoaded, filter: state.projectUIState.listUIState.filter, + onClearEntityFilterPressed: () => + store.dispatch(FilterProjectsByEntity()), + onViewEntityFilterPressed: (BuildContext context) => store.dispatch( + ViewClient( + clientId: state.invoiceListState.filterEntityId, + context: context)), onProjectTap: (context, project) { store.dispatch(ViewProject(projectId: project.id, context: context)); }, @@ -103,10 +115,14 @@ class ProjectListVM { final List projectList; final BuiltMap projectMap; final BuiltMap clientMap; + final ListUIState listState; final String filter; final bool isLoading; final bool isLoaded; final Function(BuildContext, ProjectEntity) onProjectTap; final Function(BuildContext) onRefreshed; final Function(BuildContext, ProjectEntity, EntityAction) onEntityAction; + final Function onClearEntityFilterPressed; + final Function(BuildContext) onViewEntityFilterPressed; + } diff --git a/stubs/ui/stub/stub_list b/stubs/ui/stub/stub_list index 57fc35e20..9b1d520ad 100644 --- a/stubs/ui/stub/stub_list +++ b/stubs/ui/stub/stub_list @@ -14,30 +14,11 @@ class StubList extends StatelessWidget { @required this.viewModel, }) : super(key: key); - @override - Widget build(BuildContext context) { - if (!viewModel.isLoaded) { - return LoadingIndicator(); - } else if (viewModel.stubList.isEmpty) { - return Opacity( - opacity: 0.5, - child: Center( - child: Text( - AppLocalization.of(context).noRecordsFound, - style: TextStyle( - fontSize: 18.0, - ), - ), - ), - ); - } - - return _buildListView(context); - } + final StubListVM viewModel; void _showMenu( - BuildContext context, StubEntity stub) async { - if (stub == null) { + BuildContext context, StubEntity stub, ClientEntity client) async { + if (stub == null || client == null) { return; } @@ -46,7 +27,7 @@ class StubList extends StatelessWidget { context: context, builder: (BuildContext dialogContext) => SimpleDialog( children: stub - .getEntityActions(user: user) + .getEntityActions(user: user, client: client) .map((entityAction) { if (entityAction == null) { return Divider(); @@ -71,36 +52,104 @@ class StubList extends StatelessWidget { } } - Widget _buildListView(BuildContext context) { - return RefreshIndicator( - onRefresh: () => viewModel.onRefreshed(context), - child: ListView.builder( - itemCount: viewModel.stubList.length, - itemBuilder: (BuildContext context, index) { - final stubId = viewModel.stubList[index]; - final stub = viewModel.stubMap[stubId]; - return Column(children: [ - StubListItem( - user: viewModel.user, - filter: viewModel.filter, - stub: stub, - onEntityAction: (EntityAction action) { - if (action == EntityAction.more) { - _showMenu(context, stub); - } else { - viewModel.onEntityAction(context, stub, action); - } - }, - onTap: () => viewModel.onStubTap(context, stub), - onLongPress: () => _showMenu(context, stub), - ), - Divider( - height: 1.0, - ), - ]); - }), + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + final listState = viewModel.listState; + final filteredClientId = listState.filterEntityId; + final filteredClient = + filteredClientId != null ? viewModel.clientMap[filteredClientId] : null; + + return Column( + children: [ + filteredClient != null + ? Material( + color: Colors.orangeAccent, + elevation: 6.0, + child: InkWell( + onTap: () => viewModel.onViewEntityFilterPressed(context), + child: Row( + children: [ + SizedBox(width: 18.0), + Expanded( + child: Text( + '${localization.filteredBy} ${filteredClient.listDisplayName}', + style: TextStyle( + color: Colors.white, + fontSize: 16.0, + ), + ), + ), + IconButton( + icon: Icon( + Icons.close, + color: Colors.white, + ), + onPressed: () => viewModel.onClearEntityFilterPressed(), + ) + ], + ), + ), + ) + : Container(), + Expanded( + child: !viewModel.isLoaded + ? LoadingIndicator() + : RefreshIndicator( + onRefresh: () => viewModel.onRefreshed(context), + child: viewModel.stubList.isEmpty + ? Opacity( + opacity: 0.5, + child: Center( + child: Text( + AppLocalization.of(context).noRecordsFound, + style: TextStyle( + fontSize: 18.0, + ), + ), + ), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: viewModel.stubList.length, + itemBuilder: (BuildContext context, index) { + final stubId = viewModel.stubList[index]; + final stub = viewModel.stubMap[stubId]; + final client = + viewModel.clientMap[stub.clientId] ?? + ClientEntity(); + return Column( + children: [ + StubListItem( + user: viewModel.user, + filter: viewModel.filter, + stub: stub, + client: + viewModel.clientMap[stub.clientId] ?? + ClientEntity(), + onTap: () => + viewModel.onStubTap(context, stub), + onEntityAction: (EntityAction action) { + if (action == EntityAction.more) { + _showMenu(context, stub, client); + } else { + viewModel.onEntityAction( + context, stub, action); + } + }, + onLongPress: () => + _showMenu(context, stub, client), + ), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + ), + ], ); } - - final StubListVM viewModel; } diff --git a/stubs/ui/stub/stub_list_vm b/stubs/ui/stub/stub_list_vm index e2283d0a2..d1ce33174 100644 --- a/stubs/ui/stub/stub_list_vm +++ b/stubs/ui/stub/stub_list_vm @@ -5,6 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; +import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/redux/stub/stub_selectors.dart'; @@ -38,8 +40,11 @@ class StubListVM { @required this.isLoading, @required this.isLoaded, @required this.onStubTap, + @required this.listState, @required this.onRefreshed, @required this.onEntityAction, + @required this.onClearEntityFilterPressed, + @required this.onViewEntityFilterPressed, }); static StubListVM fromStore(Store store) { @@ -57,12 +62,19 @@ class StubListVM { return StubListVM( user: state.user, + listState: state.stubListState, stubList: memoizedFilteredStubList(state.stubState.map, state.stubState.list, state.stubListState), stubMap: state.stubState.map, isLoading: state.isLoading, isLoaded: state.stubState.isLoaded, filter: state.stubUIState.listUIState.filter, + onClearEntityFilterPressed: () => + store.dispatch(FilterStubsByEntity()), + onViewEntityFilterPressed: (BuildContext context) => store.dispatch( + ViewClient( + clientId: state.stubListState.filterEntityId, + context: context)), onStubTap: (context, stub) { store.dispatch(EditStub(stub: stub, context: context)); }, @@ -100,10 +112,14 @@ class StubListVM { final UserEntity user; final List stubList; final BuiltMap stubMap; + final ListUIState listState; final String filter; final bool isLoading; final bool isLoaded; final Function(BuildContext, StubEntity) onStubTap; final Function(BuildContext) onRefreshed; final Function(BuildContext, StubEntity, EntityAction) onEntityAction; + final Function onClearEntityFilterPressed; + final Function(BuildContext) onViewEntityFilterPressed; + }