diff --git a/lib/data/models/serializers.g.dart b/lib/data/models/serializers.g.dart index 90af0843b..20b701ce4 100644 --- a/lib/data/models/serializers.g.dart +++ b/lib/data/models/serializers.g.dart @@ -309,6 +309,9 @@ Serializers _$serializers = (new Serializers().toBuilder() ..addBuilderFactory( const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(EntityType)]), + () => new ListBuilder()) ..addBuilderFactory( const FullType( BuiltList, const [const FullType(ExpenseCategoryEntity)]), @@ -428,8 +431,7 @@ Serializers _$serializers = (new Serializers().toBuilder() BuiltList, const [const FullType(InvoiceHistoryEntity)]), () => new ListBuilder()) ..addBuilderFactory( - const FullType(BuiltList, const [const FullType(LanguageEntity)]), - () => new ListBuilder()) + const FullType(BuiltList, const [const FullType(LanguageEntity)]), () => new ListBuilder()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(PaymentEntity)]), () => new ListBuilder()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(PaymentTermEntity)]), () => new ListBuilder()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(PaymentTypeEntity)]), () => new ListBuilder()) diff --git a/lib/redux/app/app_actions.dart b/lib/redux/app/app_actions.dart index 93747c3b7..087c72a04 100644 --- a/lib/redux/app/app_actions.dart +++ b/lib/redux/app/app_actions.dart @@ -159,6 +159,16 @@ class RefreshData implements StartLoading { final bool includeStatic; } +class PreviewEntity { + const PreviewEntity({ + this.entityType, + this.entityId, + }); + + final String entityId; + final EntityType entityType; +} + class ClearData {} class RefreshDataFailure implements StopLoading { @@ -332,12 +342,28 @@ void viewEntitiesByType({ }); } +void viewEntity( + {BuildContext context, + BaseEntity entity, + bool force = false, + bool addToStack = false, + BaseEntity filterEntity}) => + viewEntityById( + context: context, + entityId: entity.id, + entityType: entity.entityType, + force: force, + addToStack: addToStack, + filterEntity: filterEntity, + ); + void viewEntityById({ BuildContext context, String entityId, EntityType entityType, bool force = false, bool showError = true, + bool addToStack = false, BaseEntity filterEntity, }) { final store = StoreProvider.of(context); @@ -350,6 +376,14 @@ void viewEntityById({ context: context, force: force, callback: () { + if (addToStack) { + store.dispatch(PreviewEntity( + entityId: entityId, + entityType: entityType, + )); + return; + } + if (filterEntity != null && (uiState.filterEntityType != filterEntity.entityType || uiState.filterEntityId != filterEntity.id)) { @@ -549,19 +583,6 @@ void viewEntityById({ }); } -void viewEntity( - {BuildContext context, - BaseEntity entity, - bool force = false, - BaseEntity filterEntity}) => - viewEntityById( - context: context, - entityId: entity.id, - entityType: entity.entityType, - force: force, - filterEntity: filterEntity, - ); - void createEntityByType( {BuildContext context, EntityType entityType, bool force = false}) { final store = StoreProvider.of(context); diff --git a/lib/redux/app/app_state.dart b/lib/redux/app/app_state.dart index 195945a09..d73c7a166 100644 --- a/lib/redux/app/app_state.dart +++ b/lib/redux/app/app_state.dart @@ -714,7 +714,8 @@ abstract class AppState implements Built { return '\n\nURL: ${authState.url}' '\nRoute: ${uiState.currentRoute}' - '\nPrev: ${uiState.previousRoute}' + '\nPrevious: ${uiState.previousRoute}' + '\nPreview: ${uiState.previewStack}' '\nIs Loaded: ${isLoaded ? 'Yes' : 'No'}' '\nis Large: ${(company?.isLarge ?? false) ? 'Yes' : 'No'}' '\nCompany: $companyUpdated${userCompanyState.isStale ? ' [S]' : ''}' diff --git a/lib/redux/client/client_reducer.dart b/lib/redux/client/client_reducer.dart index d62d16d8e..10f4b3a49 100644 --- a/lib/redux/client/client_reducer.dart +++ b/lib/redux/client/client_reducer.dart @@ -43,6 +43,9 @@ final editingContactReducer = combineReducers([ ]); final selectedIdReducer = combineReducers([ + TypedReducer((selectedId, action) { + return action.entityId; + }), TypedReducer((selectedId, action) { return action.clientId; }), diff --git a/lib/redux/task/task_selectors.dart b/lib/redux/task/task_selectors.dart index ef0de4a8e..2885e22ad 100644 --- a/lib/redux/task/task_selectors.dart +++ b/lib/redux/task/task_selectors.dart @@ -137,6 +137,9 @@ List filteredTasksSelector( } else if (filterEntityType == EntityType.invoice && task.invoiceId != filterEntityId) { return false; + } else if (filterEntityType == EntityType.user && + task.assignedUserId != filterEntityId) { + return false; } } else if (task.clientId != null && !client.isActive) { return false; diff --git a/lib/redux/ui/ui_reducer.dart b/lib/redux/ui/ui_reducer.dart index b23f609da..40663e89c 100644 --- a/lib/redux/ui/ui_reducer.dart +++ b/lib/redux/ui/ui_reducer.dart @@ -1,3 +1,4 @@ +import 'package:built_collection/built_collection.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; @@ -77,6 +78,7 @@ UIState uiReducer(UIState state, dynamic action) { ? state.previousRoute : state.currentRoute ..currentRoute = currentRoute + ..previewStack.replace(previewStackReducer(state.previewStack, action)) ..productUIState.replace(productUIReducer(state.productUIState, action)) ..clientUIState.replace(clientUIReducer(state.clientUIState, action)) ..invoiceUIState.replace(invoiceUIReducer(state.invoiceUIState, action)) @@ -284,3 +286,12 @@ Reducer settingsUIReducer = combineReducers([ return state.rebuild((b) => b..tabIndex = action.tabIndex); }), ]); + +Reducer> previewStackReducer = combineReducers([ + TypedReducer, PreviewEntity>((previewStack, action) { + return BuiltList([ + ...previewStack.where((entityType) => entityType != action.entityType), + action.entityType + ]); + }), +]); diff --git a/lib/redux/ui/ui_state.dart b/lib/redux/ui/ui_state.dart index 057f7513d..40fcda3a9 100644 --- a/lib/redux/ui/ui_state.dart +++ b/lib/redux/ui/ui_state.dart @@ -1,3 +1,4 @@ +import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; @@ -43,15 +44,14 @@ abstract class UIState implements Built { filterClearedAt: 0, currentRoute: currentRoute ?? LoginScreen.route, previousRoute: '', + previewStack: BuiltList(), dashboardUIState: DashboardUIState(), productUIState: ProductUIState(), clientUIState: ClientUIState(), invoiceUIState: InvoiceUIState(), // STARTER: constructor - do not remove comment taskStatusUIState: TaskStatusUIState(), - expenseCategoryUIState: ExpenseCategoryUIState(), - recurringInvoiceUIState: RecurringInvoiceUIState(), webhookUIState: WebhookUIState(), tokenUIState: TokenUIState(), @@ -86,6 +86,8 @@ abstract class UIState implements Built { String get previousRoute; + BuiltList get previewStack; + @nullable String get filterEntityId; diff --git a/lib/redux/ui/ui_state.g.dart b/lib/redux/ui/ui_state.g.dart index f48234d75..ce3729077 100644 --- a/lib/redux/ui/ui_state.g.dart +++ b/lib/redux/ui/ui_state.g.dart @@ -27,6 +27,10 @@ class _$UIStateSerializer implements StructuredSerializer { 'previousRoute', serializers.serialize(object.previousRoute, specifiedType: const FullType(String)), + 'previewStack', + serializers.serialize(object.previewStack, + specifiedType: + const FullType(BuiltList, const [const FullType(EntityType)])), 'filterClearedAt', serializers.serialize(object.filterClearedAt, specifiedType: const FullType(int)), @@ -150,6 +154,12 @@ class _$UIStateSerializer implements StructuredSerializer { result.previousRoute = serializers.deserialize(value, specifiedType: const FullType(String)) as String; break; + case 'previewStack': + result.previewStack.replace(serializers.deserialize(value, + specifiedType: const FullType( + BuiltList, const [const FullType(EntityType)])) + as BuiltList); + break; case 'filterEntityId': result.filterEntityId = serializers.deserialize(value, specifiedType: const FullType(String)) as String; @@ -289,6 +299,8 @@ class _$UIState extends UIState { @override final String previousRoute; @override + final BuiltList previewStack; + @override final String filterEntityId; @override final EntityType filterEntityType; @@ -354,6 +366,7 @@ class _$UIState extends UIState { {this.selectedCompanyIndex, this.currentRoute, this.previousRoute, + this.previewStack, this.filterEntityId, this.filterEntityType, this.filter, @@ -393,6 +406,9 @@ class _$UIState extends UIState { if (previousRoute == null) { throw new BuiltValueNullFieldError('UIState', 'previousRoute'); } + if (previewStack == null) { + throw new BuiltValueNullFieldError('UIState', 'previewStack'); + } if (filterClearedAt == null) { throw new BuiltValueNullFieldError('UIState', 'filterClearedAt'); } @@ -487,6 +503,7 @@ class _$UIState extends UIState { selectedCompanyIndex == other.selectedCompanyIndex && currentRoute == other.currentRoute && previousRoute == other.previousRoute && + previewStack == other.previewStack && filterEntityId == other.filterEntityId && filterEntityType == other.filterEntityType && filter == other.filter && @@ -539,7 +556,7 @@ class _$UIState extends UIState { $jc( $jc( $jc( - $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), filterEntityId.hashCode), filterEntityType.hashCode), filter.hashCode), filterClearedAt.hashCode), dashboardUIState.hashCode), productUIState.hashCode), clientUIState.hashCode), invoiceUIState.hashCode), taskStatusUIState.hashCode), expenseCategoryUIState.hashCode), + $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), previewStack.hashCode), filterEntityId.hashCode), filterEntityType.hashCode), filter.hashCode), filterClearedAt.hashCode), dashboardUIState.hashCode), productUIState.hashCode), clientUIState.hashCode), invoiceUIState.hashCode), taskStatusUIState.hashCode), expenseCategoryUIState.hashCode), recurringInvoiceUIState.hashCode), webhookUIState.hashCode), tokenUIState.hashCode), @@ -567,6 +584,7 @@ class _$UIState extends UIState { ..add('selectedCompanyIndex', selectedCompanyIndex) ..add('currentRoute', currentRoute) ..add('previousRoute', previousRoute) + ..add('previewStack', previewStack) ..add('filterEntityId', filterEntityId) ..add('filterEntityType', filterEntityType) ..add('filter', filter) @@ -617,6 +635,12 @@ class UIStateBuilder implements Builder { set previousRoute(String previousRoute) => _$this._previousRoute = previousRoute; + ListBuilder _previewStack; + ListBuilder get previewStack => + _$this._previewStack ??= new ListBuilder(); + set previewStack(ListBuilder previewStack) => + _$this._previewStack = previewStack; + String _filterEntityId; String get filterEntityId => _$this._filterEntityId; set filterEntityId(String filterEntityId) => @@ -796,6 +820,7 @@ class UIStateBuilder implements Builder { _selectedCompanyIndex = _$v.selectedCompanyIndex; _currentRoute = _$v.currentRoute; _previousRoute = _$v.previousRoute; + _previewStack = _$v.previewStack?.toBuilder(); _filterEntityId = _$v.filterEntityId; _filterEntityType = _$v.filterEntityType; _filter = _$v.filter; @@ -852,6 +877,7 @@ class UIStateBuilder implements Builder { selectedCompanyIndex: selectedCompanyIndex, currentRoute: currentRoute, previousRoute: previousRoute, + previewStack: previewStack.build(), filterEntityId: filterEntityId, filterEntityType: filterEntityType, filter: filter, @@ -884,6 +910,9 @@ class UIStateBuilder implements Builder { } catch (_) { String _$failedField; try { + _$failedField = 'previewStack'; + previewStack.build(); + _$failedField = 'dashboardUIState'; dashboardUIState.build(); _$failedField = 'productUIState'; diff --git a/lib/ui/app/entities/entity_list_tile.dart b/lib/ui/app/entities/entity_list_tile.dart index d723cccf4..6b02e02d9 100644 --- a/lib/ui/app/entities/entity_list_tile.dart +++ b/lib/ui/app/entities/entity_list_tile.dart @@ -83,9 +83,11 @@ class _EntityListTileState extends State { icon: Icon(isHovered || isMobile(context) ? Icons.chevron_right : Icons.filter_list), - onPressed: isHovered - ? () => viewEntity(entity: widget.entity, context: context) - : () => null, + onPressed: () => viewEntity( + entity: widget.entity, + context: context, + addToStack: isDesktop(context), + ), color: isFilteredBy ? (state.prefState.enableDarkMode ? Colors.white diff --git a/lib/ui/task/view/task_view_overview.dart b/lib/ui/task/view/task_view_overview.dart index 464a1fd85..91d3fee44 100644 --- a/lib/ui/task/view/task_view_overview.dart +++ b/lib/ui/task/view/task_view_overview.dart @@ -55,6 +55,7 @@ class _TaskOverviewState extends State { final client = viewModel.client; final company = viewModel.company; final invoice = viewModel.state.invoiceState.map[task.invoiceId]; + final user = viewModel.state.userState.map[task.assignedUserId]; final Map fields = { TaskFields.rate: formatNumber(task.rate, context, zeroIsNull: true), @@ -126,6 +127,15 @@ class _TaskOverviewState extends State { ]); } + if (user != null) { + widgets.addAll([ + EntityListTile( + entity: user, + isFilter: widget.isFilter, + ), + ]); + } + if (invoice != null) { widgets.addAll([ EntityListTile(