This commit is contained in:
Hillel Coren 2018-12-17 17:10:39 +02:00
parent ec23625c60
commit 251617d0d7
4 changed files with 239 additions and 110 deletions

View File

@ -14,30 +14,11 @@ class ProjectList extends StatelessWidget {
@required this.viewModel, @required this.viewModel,
}) : super(key: key); }) : super(key: key);
@override final ProjectListVM viewModel;
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);
}
void _showMenu( void _showMenu(
BuildContext context, ProjectEntity project) async { BuildContext context, ProjectEntity project, ClientEntity client) async {
if (project == null) { if (project == null || client == null) {
return; return;
} }
@ -45,9 +26,9 @@ class ProjectList extends StatelessWidget {
final message = await showDialog<String>( final message = await showDialog<String>(
context: context, context: context,
builder: (BuildContext dialogContext) => SimpleDialog( builder: (BuildContext dialogContext) => SimpleDialog(
children: project children: project
.getEntityActions(user: user) .getEntityActions(user: user, client: client)
.map((entityAction) { .map((entityAction) {
if (entityAction == null) { if (entityAction == null) {
return Divider(); return Divider();
} else { } else {
@ -66,42 +47,109 @@ class ProjectList extends StatelessWidget {
if (message != null) { if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar( Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow( content: SnackBarRow(
message: message, message: message,
))); )));
} }
} }
Widget _buildListView(BuildContext context) { @override
return RefreshIndicator( Widget build(BuildContext context) {
onRefresh: () => viewModel.onRefreshed(context), final localization = AppLocalization.of(context);
child: ListView.builder( final listState = viewModel.listState;
itemCount: viewModel.projectList.length, final filteredClientId = listState.filterEntityId;
itemBuilder: (BuildContext context, index) { final filteredClient =
final projectId = viewModel.projectList[index]; filteredClientId != null ? viewModel.clientMap[filteredClientId] : null;
final project = viewModel.projectMap[projectId];
return Column(children: <Widget>[ return Column(
ProjectListItem( children: <Widget>[
user: viewModel.user, filteredClient != null
filter: viewModel.filter, ? Material(
project: project, color: Colors.orangeAccent,
client: viewModel.clientMap[project.clientId], elevation: 6.0,
onEntityAction: (EntityAction action) { child: InkWell(
if (action == EntityAction.more) { onTap: () => viewModel.onViewEntityFilterPressed(context),
_showMenu(context, project); child: Row(
} else { children: <Widget>[
viewModel.onEntityAction(context, project, action); SizedBox(width: 18.0),
} Expanded(
}, child: Text(
onTap: () => viewModel.onProjectTap(context, project), '${localization.filteredBy} ${filteredClient.listDisplayName}',
onLongPress: () => _showMenu(context, project), 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: <Widget>[
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;
} }

View File

@ -1,4 +1,6 @@
import 'dart:async'; 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:redux/redux.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -35,12 +37,15 @@ class ProjectListVM {
@required this.projectList, @required this.projectList,
@required this.projectMap, @required this.projectMap,
@required this.clientMap, @required this.clientMap,
@required this.listState,
@required this.filter, @required this.filter,
@required this.isLoading, @required this.isLoading,
@required this.isLoaded, @required this.isLoaded,
@required this.onProjectTap, @required this.onProjectTap,
@required this.onRefreshed, @required this.onRefreshed,
@required this.onEntityAction, @required this.onEntityAction,
@required this.onClearEntityFilterPressed,
@required this.onViewEntityFilterPressed,
}); });
static ProjectListVM fromStore(Store<AppState> store) { static ProjectListVM fromStore(Store<AppState> store) {
@ -58,6 +63,7 @@ class ProjectListVM {
return ProjectListVM( return ProjectListVM(
user: state.user, user: state.user,
listState: state.projectListState,
projectList: memoizedFilteredProjectList(state.projectState.map, projectList: memoizedFilteredProjectList(state.projectState.map,
state.projectState.list, state.projectListState, state.clientState.map), state.projectState.list, state.projectListState, state.clientState.map),
projectMap: state.projectState.map, projectMap: state.projectState.map,
@ -65,6 +71,12 @@ class ProjectListVM {
isLoading: state.isLoading, isLoading: state.isLoading,
isLoaded: state.projectState.isLoaded, isLoaded: state.projectState.isLoaded,
filter: state.projectUIState.listUIState.filter, filter: state.projectUIState.listUIState.filter,
onClearEntityFilterPressed: () =>
store.dispatch(FilterProjectsByEntity()),
onViewEntityFilterPressed: (BuildContext context) => store.dispatch(
ViewClient(
clientId: state.invoiceListState.filterEntityId,
context: context)),
onProjectTap: (context, project) { onProjectTap: (context, project) {
store.dispatch(ViewProject(projectId: project.id, context: context)); store.dispatch(ViewProject(projectId: project.id, context: context));
}, },
@ -103,10 +115,14 @@ class ProjectListVM {
final List<int> projectList; final List<int> projectList;
final BuiltMap<int, ProjectEntity> projectMap; final BuiltMap<int, ProjectEntity> projectMap;
final BuiltMap<int, ClientEntity> clientMap; final BuiltMap<int, ClientEntity> clientMap;
final ListUIState listState;
final String filter; final String filter;
final bool isLoading; final bool isLoading;
final bool isLoaded; final bool isLoaded;
final Function(BuildContext, ProjectEntity) onProjectTap; final Function(BuildContext, ProjectEntity) onProjectTap;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function(BuildContext, ProjectEntity, EntityAction) onEntityAction; final Function(BuildContext, ProjectEntity, EntityAction) onEntityAction;
final Function onClearEntityFilterPressed;
final Function(BuildContext) onViewEntityFilterPressed;
} }

View File

@ -14,30 +14,11 @@ class StubList extends StatelessWidget {
@required this.viewModel, @required this.viewModel,
}) : super(key: key); }) : super(key: key);
@override final StubListVM viewModel;
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);
}
void _showMenu( void _showMenu(
BuildContext context, StubEntity stub) async { BuildContext context, StubEntity stub, ClientEntity client) async {
if (stub == null) { if (stub == null || client == null) {
return; return;
} }
@ -46,7 +27,7 @@ class StubList extends StatelessWidget {
context: context, context: context,
builder: (BuildContext dialogContext) => SimpleDialog( builder: (BuildContext dialogContext) => SimpleDialog(
children: stub children: stub
.getEntityActions(user: user) .getEntityActions(user: user, client: client)
.map((entityAction) { .map((entityAction) {
if (entityAction == null) { if (entityAction == null) {
return Divider(); return Divider();
@ -71,36 +52,104 @@ class StubList extends StatelessWidget {
} }
} }
Widget _buildListView(BuildContext context) { @override
return RefreshIndicator( Widget build(BuildContext context) {
onRefresh: () => viewModel.onRefreshed(context), final localization = AppLocalization.of(context);
child: ListView.builder( final listState = viewModel.listState;
itemCount: viewModel.stubList.length, final filteredClientId = listState.filterEntityId;
itemBuilder: (BuildContext context, index) { final filteredClient =
final stubId = viewModel.stubList[index]; filteredClientId != null ? viewModel.clientMap[filteredClientId] : null;
final stub = viewModel.stubMap[stubId];
return Column(children: <Widget>[ return Column(
StubListItem( children: <Widget>[
user: viewModel.user, filteredClient != null
filter: viewModel.filter, ? Material(
stub: stub, color: Colors.orangeAccent,
onEntityAction: (EntityAction action) { elevation: 6.0,
if (action == EntityAction.more) { child: InkWell(
_showMenu(context, stub); onTap: () => viewModel.onViewEntityFilterPressed(context),
} else { child: Row(
viewModel.onEntityAction(context, stub, action); children: <Widget>[
} SizedBox(width: 18.0),
}, Expanded(
onTap: () => viewModel.onStubTap(context, stub), child: Text(
onLongPress: () => _showMenu(context, stub), '${localization.filteredBy} ${filteredClient.listDisplayName}',
), style: TextStyle(
Divider( color: Colors.white,
height: 1.0, 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: <Widget>[
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;
} }

View File

@ -5,6 +5,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:built_collection/built_collection.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/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/stub/stub_selectors.dart'; import 'package:invoiceninja_flutter/redux/stub/stub_selectors.dart';
@ -38,8 +40,11 @@ class StubListVM {
@required this.isLoading, @required this.isLoading,
@required this.isLoaded, @required this.isLoaded,
@required this.onStubTap, @required this.onStubTap,
@required this.listState,
@required this.onRefreshed, @required this.onRefreshed,
@required this.onEntityAction, @required this.onEntityAction,
@required this.onClearEntityFilterPressed,
@required this.onViewEntityFilterPressed,
}); });
static StubListVM fromStore(Store<AppState> store) { static StubListVM fromStore(Store<AppState> store) {
@ -57,12 +62,19 @@ class StubListVM {
return StubListVM( return StubListVM(
user: state.user, user: state.user,
listState: state.stubListState,
stubList: memoizedFilteredStubList(state.stubState.map, stubList: memoizedFilteredStubList(state.stubState.map,
state.stubState.list, state.stubListState), state.stubState.list, state.stubListState),
stubMap: state.stubState.map, stubMap: state.stubState.map,
isLoading: state.isLoading, isLoading: state.isLoading,
isLoaded: state.stubState.isLoaded, isLoaded: state.stubState.isLoaded,
filter: state.stubUIState.listUIState.filter, filter: state.stubUIState.listUIState.filter,
onClearEntityFilterPressed: () =>
store.dispatch(FilterStubsByEntity()),
onViewEntityFilterPressed: (BuildContext context) => store.dispatch(
ViewClient(
clientId: state.stubListState.filterEntityId,
context: context)),
onStubTap: (context, stub) { onStubTap: (context, stub) {
store.dispatch(EditStub(stub: stub, context: context)); store.dispatch(EditStub(stub: stub, context: context));
}, },
@ -100,10 +112,14 @@ class StubListVM {
final UserEntity user; final UserEntity user;
final List<int> stubList; final List<int> stubList;
final BuiltMap<int, StubEntity> stubMap; final BuiltMap<int, StubEntity> stubMap;
final ListUIState listState;
final String filter; final String filter;
final bool isLoading; final bool isLoading;
final bool isLoaded; final bool isLoaded;
final Function(BuildContext, StubEntity) onStubTap; final Function(BuildContext, StubEntity) onStubTap;
final Function(BuildContext) onRefreshed; final Function(BuildContext) onRefreshed;
final Function(BuildContext, StubEntity, EntityAction) onEntityAction; final Function(BuildContext, StubEntity, EntityAction) onEntityAction;
final Function onClearEntityFilterPressed;
final Function(BuildContext) onViewEntityFilterPressed;
} }