Working on dashboard

This commit is contained in:
unknown 2018-05-22 04:30:05 -07:00
parent 15775f1421
commit 314171504e
11 changed files with 223 additions and 41 deletions

View File

@ -3,18 +3,31 @@ import 'package:json_annotation/json_annotation.dart';
part 'entities.g.dart'; part 'entities.g.dart';
@JsonSerializable() @JsonSerializable()
class BaseResponse extends Object with _$BaseResponseSerializerMixin { class BaseListResponse extends Object with _$BaseListResponseSerializerMixin {
//final String message; //final String message;
@JsonKey(name: "data") @JsonKey(name: "data")
final List<dynamic> data; final List<dynamic> data;
BaseResponse( BaseListResponse(
//this.message,
this.data, this.data,
); );
factory BaseResponse.fromJson(Map<String, dynamic> json) => _$BaseResponseFromJson(json); factory BaseListResponse.fromJson(Map<String, dynamic> 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<String, dynamic> json) => _$BaseItemResponseFromJson(json);
} }
@JsonSerializable() @JsonSerializable()

View File

@ -6,14 +6,22 @@ part of 'entities.dart';
// Generator: JsonSerializableGenerator // Generator: JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
BaseResponse _$BaseResponseFromJson(Map<String, dynamic> json) => BaseListResponse _$BaseListResponseFromJson(Map<String, dynamic> json) =>
new BaseResponse(json['data'] as List); new BaseListResponse(json['data'] as List);
abstract class _$BaseResponseSerializerMixin { abstract class _$BaseListResponseSerializerMixin {
List<dynamic> get data; List<dynamic> get data;
Map<String, dynamic> toJson() => <String, dynamic>{'data': data}; Map<String, dynamic> toJson() => <String, dynamic>{'data': data};
} }
BaseItemResponse _$BaseItemResponseFromJson(Map<String, dynamic> json) =>
new BaseItemResponse(json['data']);
abstract class _$BaseItemResponseSerializerMixin {
dynamic get data;
Map<String, dynamic> toJson() => <String, dynamic>{'data': data};
}
ProductEntity _$ProductEntityFromJson(Map<String, dynamic> json) => ProductEntity _$ProductEntityFromJson(Map<String, dynamic> json) =>
new ProductEntity(json['id'] as int) new ProductEntity(json['id'] as int)
..productKey = json['product_key'] as String ..productKey = json['product_key'] as String

View File

@ -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<dynamic> 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<List<dynamic>> loadItems(dynamic product) {
return Future.wait<dynamic>([]);
}
// Persists products to local disk and the web
@override
Future saveData(List<dynamic> products) {
return Future.wait<dynamic>([]);
}
}

View File

@ -22,10 +22,10 @@ class ProductsRepositoryFlutter implements BaseRepository {
/// Loads products first from File storage. If they don't exist or encounter an /// 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. /// error, it attempts to load the Products from a Web Client.
@override @override
Future<List<dynamic>> loadData(AuthState auth) async { Future<List<dynamic>> loadItems(AuthState auth) async {
print('ProductRepo: loadProducts...'); print('ProductRepo: loadProducts...');
final products = await webClient.fetchData( final products = await webClient.fetchList(
auth.url + '/products', auth.token); auth.url + '/products', auth.token);
//fileStorage.saveProducts(products); //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<dynamic>([]);
}
@override @override
Future saveData(List<dynamic> products) { Future saveData(List<dynamic> products) {
return Future.wait<dynamic>([ return Future.wait<dynamic>([

View File

@ -13,10 +13,10 @@ import 'package:invoiceninja/data/models/entities.dart';
/// inject the correct implementation depending on the environment, such as /// inject the correct implementation depending on the environment, such as
/// web or Flutter. /// web or Flutter.
abstract class BaseRepository { 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<List<dynamic>> loadData(AuthState auth);
// Persists products to local disk and the web Future<List<dynamic>> loadItems(AuthState auth);
Future<dynamic> loadItem(AuthState auth);
Future saveData(List<dynamic> data); Future saveData(List<dynamic> data);
} }

View File

@ -12,15 +12,23 @@ class WebClient {
const WebClient(); const WebClient();
/// Mock that "fetches" some Products from a "web service" after a short delay Future<http.Response> sendRequest(String url, String token) {
Future<List<dynamic>> fetchData(String url, String token) async { return http.Client().get(
final response = await http.Client().get(
url, url,
headers: {'X-Ninja-Token': token}, headers: {'X-Ninja-Token': token},
); );
}
return BaseResponse Future<dynamic> fetchItem(String url, String token) async {
final http.Response response = await sendRequest(url, token);
return BaseItemResponse
.fromJson(json.decode(response.body))
.data;
}
Future<List<dynamic>> fetchList(String url, String token) async {
final http.Response response = await sendRequest(url, token);
return BaseListResponse
.fromJson(json.decode(response.body)) .fromJson(json.decode(response.body))
.data .data
.toList(); .toList();

View File

@ -1,17 +1,20 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/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/dashboard/dashboard.dart';
import 'package:invoiceninja/ui/client/clients.dart'; import 'package:invoiceninja/ui/client/clients.dart';
import 'package:invoiceninja/ui/product/products.dart'; import 'package:invoiceninja/ui/product/products.dart';
import 'package:invoiceninja/ui/auth/login.dart'; import 'package:invoiceninja/redux/app/app_reducer.dart';
import 'package:invoiceninja/routes.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_actions.dart';
import 'package:invoiceninja/redux/product/product_middleware.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() { void main() {
@ -29,6 +32,7 @@ class InvoiceNinjaApp extends StatelessWidget {
initialState: AppState.loading(), initialState: AppState.loading(),
middleware: [] middleware: []
..addAll(createStoreAuthMiddleware()) ..addAll(createStoreAuthMiddleware())
..addAll(createStoreDashboardMiddleware())
..addAll(createStoreProductsMiddleware()) ..addAll(createStoreProductsMiddleware())
..addAll([ ..addAll([
LoggingMiddleware.printer(), LoggingMiddleware.printer(),
@ -59,6 +63,7 @@ class InvoiceNinjaApp extends StatelessWidget {
}, },
AppRoutes.dashboard: (context) { AppRoutes.dashboard: (context) {
return StoreBuilder<AppState>( return StoreBuilder<AppState>(
onInit: (store) => store.dispatch(LoadDashboardAction()),
builder: (context, store) { builder: (context, store) {
return Dashboard(); return Dashboard();
}, },

View File

@ -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<ProductEntity> products;
DashboardLoadedAction(this.products);
@override
String toString() {
return 'DashboardLoadedAction{products: $products}';
}
}

View File

@ -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<Middleware<AppState>> createStoreDashboardMiddleware([
BaseRepository repository = const DashboardRepositoryFlutter(
fileStorage: const FileStorage(
'__invoiceninja__',
getApplicationDocumentsDirectory,
),
),
]) {
final loadDashboard = _createLoadDashboard(repository);
return [
TypedMiddleware<AppState, LoadDashboardAction>(loadDashboard),
];
}
Middleware<AppState> _createSaveDashboard(BaseRepository repository) {
return (Store<AppState> store, action, NextDispatcher next) {
next(action);
/*
repository.saveDashboard(
productsSelector(store.state).map((product) => product.toEntity()).toList(),
);
*/
};
}
Middleware<AppState> _createLoadDashboard(BaseRepository repository) {
return (Store<AppState> 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);
};
}

View File

@ -40,9 +40,7 @@ Middleware<AppState> _createSaveProducts(BaseRepository repository) {
Middleware<AppState> _createLoadProducts(BaseRepository repository) { Middleware<AppState> _createLoadProducts(BaseRepository repository) {
return (Store<AppState> store, action, NextDispatcher next) { return (Store<AppState> store, action, NextDispatcher next) {
print('Product Middleware: createLoadProducts...'); repository.loadItems(store.state.auth).then(
repository.loadData(store.state.auth).then(
(products) { (products) {
store.dispatch( store.dispatch(
ProductsLoadedAction( ProductsLoadedAction(

View File

@ -7,21 +7,28 @@ class Dashboard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return Scaffold(
appBar: new AppBar( appBar: AppBar(
title: new Text('Dashboard'), title: Text('Dashboard'),
), ),
drawer: new Sidebar(), drawer: Sidebar(),
body: new Center( body: Column(
child: new Column( children: <Widget>[
mainAxisAlignment: MainAxisAlignment.center, Card(
children: [ child: Column(
new Text( mainAxisSize: MainAxisSize.min,
'DASHBOARD', children: <Widget>[
const ListTile(
leading: const Icon(Icons.album),
title: const Text('The Enchanted Nightingale'),
subtitle: const Text(
'Music by Julie Gable. Lyrics by Sidney Stein.'),
),
],
), ),
], ),
), ],
), ),
); );
} }
} }