From 314171504e0e18b9bca10e64c5fceba285c4f887 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 May 2018 04:30:05 -0700 Subject: [PATCH] Working on dashboard --- lib/data/models/entities.dart | 21 +++++-- lib/data/models/entities.g.dart | 14 ++++- .../repositories/dashboard_repository.dart | 60 +++++++++++++++++++ lib/data/repositories/product_repository.dart | 10 +++- lib/data/repositories/repositories.dart | 8 +-- lib/data/web_client.dart | 18 ++++-- lib/main.dart | 17 ++++-- lib/redux/dashboard/dashboard_actions.dart | 25 ++++++++ lib/redux/dashboard/dashboard_middleware.dart | 54 +++++++++++++++++ lib/redux/product/product_middleware.dart | 4 +- lib/ui/dashboard/dashboard.dart | 33 ++++++---- 11 files changed, 223 insertions(+), 41 deletions(-) create mode 100644 lib/data/repositories/dashboard_repository.dart create mode 100644 lib/redux/dashboard/dashboard_actions.dart create mode 100644 lib/redux/dashboard/dashboard_middleware.dart diff --git a/lib/data/models/entities.dart b/lib/data/models/entities.dart index 8d72f2da5..d73f992c7 100644 --- a/lib/data/models/entities.dart +++ b/lib/data/models/entities.dart @@ -3,18 +3,31 @@ import 'package:json_annotation/json_annotation.dart'; part 'entities.g.dart'; @JsonSerializable() -class BaseResponse extends Object with _$BaseResponseSerializerMixin { +class BaseListResponse extends Object with _$BaseListResponseSerializerMixin { //final String message; @JsonKey(name: "data") final List data; - BaseResponse( - //this.message, + BaseListResponse( this.data, ); - factory BaseResponse.fromJson(Map json) => _$BaseResponseFromJson(json); + factory BaseListResponse.fromJson(Map json) => _$BaseListResponseFromJson(json); +} + +@JsonSerializable() +class BaseItemResponse extends Object with _$BaseItemResponseSerializerMixin { + //final String message; + + @JsonKey(name: "data") + final dynamic data; + + BaseItemResponse( + this.data, + ); + + factory BaseItemResponse.fromJson(Map json) => _$BaseItemResponseFromJson(json); } @JsonSerializable() diff --git a/lib/data/models/entities.g.dart b/lib/data/models/entities.g.dart index dcbf30093..c7c245af8 100644 --- a/lib/data/models/entities.g.dart +++ b/lib/data/models/entities.g.dart @@ -6,14 +6,22 @@ part of 'entities.dart'; // Generator: JsonSerializableGenerator // ************************************************************************** -BaseResponse _$BaseResponseFromJson(Map json) => - new BaseResponse(json['data'] as List); +BaseListResponse _$BaseListResponseFromJson(Map json) => + new BaseListResponse(json['data'] as List); -abstract class _$BaseResponseSerializerMixin { +abstract class _$BaseListResponseSerializerMixin { List get data; Map toJson() => {'data': data}; } +BaseItemResponse _$BaseItemResponseFromJson(Map json) => + new BaseItemResponse(json['data']); + +abstract class _$BaseItemResponseSerializerMixin { + dynamic get data; + Map toJson() => {'data': data}; +} + ProductEntity _$ProductEntityFromJson(Map json) => new ProductEntity(json['id'] as int) ..productKey = json['product_key'] as String diff --git a/lib/data/repositories/dashboard_repository.dart b/lib/data/repositories/dashboard_repository.dart new file mode 100644 index 000000000..3dd6ca8b0 --- /dev/null +++ b/lib/data/repositories/dashboard_repository.dart @@ -0,0 +1,60 @@ +import 'dart:async'; +import 'dart:core'; +import 'package:meta/meta.dart'; + +import 'package:invoiceninja/redux/auth/auth_state.dart'; +import 'package:invoiceninja/data/models/entities.dart'; +import 'package:invoiceninja/data/repositories/repositories.dart'; +import 'package:invoiceninja/data/file_storage.dart'; +import 'package:invoiceninja/data/web_client.dart'; + +/// A class that glues together our local file storage and web client. It has a +/// clear responsibility: Load Dashboard and Persist products. +class DashboardRepositoryFlutter implements BaseRepository { + final FileStorage fileStorage; + final WebClient webClient; + + const DashboardRepositoryFlutter({ + @required this.fileStorage, + this.webClient = const WebClient(), + }); + + /// Loads products first from File storage. If they don't exist or encounter an + /// error, it attempts to load the Dashboard from a Web Client. + @override + Future loadItem(AuthState auth) async { + print('ProductRepo: loadDashboard...'); + + final data = await webClient.fetchItem( + auth.url + '/dashboard', auth.token); + + //fileStorage.saveDashboard(products); + print('== LOAD DASHBOARD =='); + print(data); + return data.map((data) => ProductEntity.fromJson(data)).toList(); + + /* + try { + return await fileStorage.loadData(); + } catch (exception) { + final products = await webClient.fetchData( + auth.url + '/products', auth.token); + + //fileStorage.saveDashboard(products); + + return products; + } + */ + } + + @override + Future> loadItems(dynamic product) { + return Future.wait([]); + } + + // Persists products to local disk and the web + @override + Future saveData(List products) { + return Future.wait([]); + } +} \ No newline at end of file diff --git a/lib/data/repositories/product_repository.dart b/lib/data/repositories/product_repository.dart index c235ffa0b..1329b3542 100644 --- a/lib/data/repositories/product_repository.dart +++ b/lib/data/repositories/product_repository.dart @@ -22,10 +22,10 @@ class ProductsRepositoryFlutter implements BaseRepository { /// Loads products first from File storage. If they don't exist or encounter an /// error, it attempts to load the Products from a Web Client. @override - Future> loadData(AuthState auth) async { + Future> loadItems(AuthState auth) async { print('ProductRepo: loadProducts...'); - final products = await webClient.fetchData( + final products = await webClient.fetchList( auth.url + '/products', auth.token); //fileStorage.saveProducts(products); @@ -46,7 +46,11 @@ class ProductsRepositoryFlutter implements BaseRepository { */ } - // Persists products to local disk and the web + @override + Future loadItem(dynamic product) { + return Future.wait([]); + } + @override Future saveData(List products) { return Future.wait([ diff --git a/lib/data/repositories/repositories.dart b/lib/data/repositories/repositories.dart index 406b7e16a..9210bb69d 100644 --- a/lib/data/repositories/repositories.dart +++ b/lib/data/repositories/repositories.dart @@ -13,10 +13,10 @@ import 'package:invoiceninja/data/models/entities.dart'; /// inject the correct implementation depending on the environment, such as /// web or Flutter. abstract class BaseRepository { - /// Loads products first from File storage. If they don't exist or encounter an - /// error, it attempts to load the Todos from a Web Client. - Future> loadData(AuthState auth); - // Persists products to local disk and the web + Future> loadItems(AuthState auth); + + Future loadItem(AuthState auth); + Future saveData(List data); } diff --git a/lib/data/web_client.dart b/lib/data/web_client.dart index 07e85433b..e16218104 100644 --- a/lib/data/web_client.dart +++ b/lib/data/web_client.dart @@ -12,15 +12,23 @@ class WebClient { const WebClient(); - /// Mock that "fetches" some Products from a "web service" after a short delay - Future> fetchData(String url, String token) async { - - final response = await http.Client().get( + Future sendRequest(String url, String token) { + return http.Client().get( url, headers: {'X-Ninja-Token': token}, ); + } - return BaseResponse + Future fetchItem(String url, String token) async { + final http.Response response = await sendRequest(url, token); + return BaseItemResponse + .fromJson(json.decode(response.body)) + .data; + } + + Future> fetchList(String url, String token) async { + final http.Response response = await sendRequest(url, token); + return BaseListResponse .fromJson(json.decode(response.body)) .data .toList(); diff --git a/lib/main.dart b/lib/main.dart index a8cf7ab77..43536781b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,17 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:redux/redux.dart'; -import 'package:invoiceninja/redux/app/app_state.dart'; +import 'package:redux_logging/redux_logging.dart'; +import 'package:invoiceninja/routes.dart'; +import 'package:invoiceninja/ui/auth/login.dart'; import 'package:invoiceninja/ui/dashboard/dashboard.dart'; import 'package:invoiceninja/ui/client/clients.dart'; import 'package:invoiceninja/ui/product/products.dart'; -import 'package:invoiceninja/ui/auth/login.dart'; -import 'package:invoiceninja/routes.dart'; +import 'package:invoiceninja/redux/app/app_reducer.dart'; +import 'package:invoiceninja/redux/app/app_state.dart'; +import 'package:invoiceninja/redux/auth/auth_middleware.dart'; +import 'package:invoiceninja/redux/dashboard/dashboard_actions.dart'; +import 'package:invoiceninja/redux/dashboard/dashboard_middleware.dart'; import 'package:invoiceninja/redux/product/product_actions.dart'; import 'package:invoiceninja/redux/product/product_middleware.dart'; -import 'package:invoiceninja/redux/auth/auth_middleware.dart'; -import 'package:invoiceninja/redux/app/app_reducer.dart'; -import 'package:redux_logging/redux_logging.dart'; + void main() { @@ -29,6 +32,7 @@ class InvoiceNinjaApp extends StatelessWidget { initialState: AppState.loading(), middleware: [] ..addAll(createStoreAuthMiddleware()) + ..addAll(createStoreDashboardMiddleware()) ..addAll(createStoreProductsMiddleware()) ..addAll([ LoggingMiddleware.printer(), @@ -59,6 +63,7 @@ class InvoiceNinjaApp extends StatelessWidget { }, AppRoutes.dashboard: (context) { return StoreBuilder( + onInit: (store) => store.dispatch(LoadDashboardAction()), builder: (context, store) { return Dashboard(); }, diff --git a/lib/redux/dashboard/dashboard_actions.dart b/lib/redux/dashboard/dashboard_actions.dart new file mode 100644 index 000000000..1a70ef69a --- /dev/null +++ b/lib/redux/dashboard/dashboard_actions.dart @@ -0,0 +1,25 @@ +import 'package:invoiceninja/data/models/models.dart'; + +class LoadDashboardAction {} + +class DashboardNotLoadedAction { + final dynamic error; + + DashboardNotLoadedAction(this.error); + + @override + String toString() { + return 'DashboardNotLoadedAction{products: $error}'; + } +} + +class DashboardLoadedAction { + final List products; + + DashboardLoadedAction(this.products); + + @override + String toString() { + return 'DashboardLoadedAction{products: $products}'; + } +} \ No newline at end of file diff --git a/lib/redux/dashboard/dashboard_middleware.dart b/lib/redux/dashboard/dashboard_middleware.dart new file mode 100644 index 000000000..095165dee --- /dev/null +++ b/lib/redux/dashboard/dashboard_middleware.dart @@ -0,0 +1,54 @@ +import 'package:path_provider/path_provider.dart'; +import 'package:redux/redux.dart'; +import 'package:invoiceninja/redux/dashboard/dashboard_actions.dart'; +import 'package:invoiceninja/redux/app/app_state.dart'; +import 'package:invoiceninja/data/repositories/repositories.dart'; +import 'package:invoiceninja/data/repositories/dashboard_repository.dart'; +import 'package:invoiceninja/data/file_storage.dart'; +import 'package:invoiceninja/redux/product/product_selectors.dart'; +import 'package:invoiceninja/data/models/entities.dart'; + +List> createStoreDashboardMiddleware([ + BaseRepository repository = const DashboardRepositoryFlutter( + fileStorage: const FileStorage( + '__invoiceninja__', + getApplicationDocumentsDirectory, + ), + ), +]) { + final loadDashboard = _createLoadDashboard(repository); + + return [ + TypedMiddleware(loadDashboard), + ]; +} + +Middleware _createSaveDashboard(BaseRepository repository) { + return (Store store, action, NextDispatcher next) { + next(action); + + /* + repository.saveDashboard( + productsSelector(store.state).map((product) => product.toEntity()).toList(), + ); + */ + }; +} + +Middleware _createLoadDashboard(BaseRepository repository) { + return (Store store, action, NextDispatcher next) { + + repository.loadItems(store.state.auth).then( + (data) { + store.dispatch( + DashboardLoadedAction( + //products.map(ProductEntity.fromEntity).toList(), + data, + ), + ); + }, + ).catchError((error) => store.dispatch(DashboardNotLoadedAction(error))); + + next(action); + }; +} diff --git a/lib/redux/product/product_middleware.dart b/lib/redux/product/product_middleware.dart index 74d6cc6a7..a9a0df21a 100644 --- a/lib/redux/product/product_middleware.dart +++ b/lib/redux/product/product_middleware.dart @@ -40,9 +40,7 @@ Middleware _createSaveProducts(BaseRepository repository) { Middleware _createLoadProducts(BaseRepository repository) { return (Store store, action, NextDispatcher next) { - print('Product Middleware: createLoadProducts...'); - - repository.loadData(store.state.auth).then( + repository.loadItems(store.state.auth).then( (products) { store.dispatch( ProductsLoadedAction( diff --git a/lib/ui/dashboard/dashboard.dart b/lib/ui/dashboard/dashboard.dart index 1bb802452..368cc584e 100644 --- a/lib/ui/dashboard/dashboard.dart +++ b/lib/ui/dashboard/dashboard.dart @@ -7,21 +7,28 @@ class Dashboard extends StatelessWidget { @override Widget build(BuildContext context) { - return new Scaffold( - appBar: new AppBar( - title: new Text('Dashboard'), + return Scaffold( + appBar: AppBar( + title: Text('Dashboard'), ), - drawer: new Sidebar(), - body: new Center( - child: new Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - new Text( - 'DASHBOARD', + drawer: Sidebar(), + body: Column( + children: [ + Card( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ListTile( + leading: const Icon(Icons.album), + title: const Text('The Enchanted Nightingale'), + subtitle: const Text( + 'Music by Julie Gable. Lyrics by Sidney Stein.'), + ), + ], ), - ], - ), + ), + ], ), ); } -} \ No newline at end of file +}