diff --git a/lib/core/entity/Category.dart b/lib/core/entity/Category.dart new file mode 100644 index 0000000..edecff4 --- /dev/null +++ b/lib/core/entity/Category.dart @@ -0,0 +1,33 @@ +import 'package:sqflite/sqflite.dart'; + +const String Category_tableName = 'goods_category'; +const String Category_columnId = 'id'; +const String Category_columnParentIn = 'parent_id'; +const String Category_columnName = 'name'; + +class Category { + int id; + int parentIn; + String name; + + Map toMap() { + var map = { + Category_columnParentIn: parentIn, + Category_columnName: name, + }; + if (id != null) { + map[Category_columnId] = id; + } + return map; + } + + Category(); + + Category.fromMap(Map map) { + id = map[Category_columnId]; + parentIn = map[Category_columnParentIn]; + name = map[Category_columnName]; + } + +} + diff --git a/lib/core/entity/Goods.dart b/lib/core/entity/Goods.dart new file mode 100644 index 0000000..ddca0fb --- /dev/null +++ b/lib/core/entity/Goods.dart @@ -0,0 +1,45 @@ +import 'package:sqflite/sqflite.dart'; + +const String Goog_tableName = 'goods'; +const String Goog_columnId = 'id'; +const String Goog_columnArticul = 'articul'; +const String Goog_columnName = 'name'; +const String Goog_columnPrice = 'price'; +const String Goog_columnCategoryId = 'category_id'; +const String Goog_columnEan = 'ean'; + +class Good { + int id; + int articul; + String name; + double price; + int categoryId; + String ean; + + Map toMap() { + var map = { + Goog_columnArticul: articul, + Goog_columnName: name, + Goog_columnPrice: price, + Goog_columnCategoryId: categoryId, + Goog_columnEan: ean, + }; + if (id != null) { + map[Goog_columnId] = id; + } + return map; + } + + Good(); + + Good.fromMap(Map map) { + id = map[Goog_columnId]; + articul = map[Goog_columnArticul]; + name = map[Goog_columnName]; + price = map[Goog_columnPrice]?.toDouble(); + categoryId = map[Goog_columnCategoryId]; + ean = map[Goog_columnEan]; + } + +} + diff --git a/lib/core/entity/Service.dart b/lib/core/entity/Service.dart new file mode 100644 index 0000000..254c902 --- /dev/null +++ b/lib/core/entity/Service.dart @@ -0,0 +1,39 @@ +import 'package:sqflite/sqflite.dart'; + +const String Service_tableName = 'services'; +const String Service_columnId = 'id'; +const String Service_columnArticul = 'articul'; +const String Service_columnName = 'name'; +const String Service_columnPrice = 'price'; +const String Service_columnCategoryId = 'category_id'; +const String Service_columnEan = 'ean'; + +class Service { + int id; + int articul; + String name; + double price; + + Map toMap() { + var map = { + Service_columnArticul: articul, + Service_columnName: name, + Service_columnPrice: price, + }; + if (id != null) { + map[Service_columnId] = id; + } + return map; + } + + Service(); + + Service.fromMap(Map map) { + id = map[Service_columnId]; + articul = map[Service_columnArticul]; + name = map[Service_columnName]; + price = map[Service_columnPrice]?.toDouble(); + } + +} + diff --git a/lib/core/locator.dart b/lib/core/locator.dart index 09e011e..f09757e 100644 --- a/lib/core/locator.dart +++ b/lib/core/locator.dart @@ -1,5 +1,9 @@ +import 'package:aman_kassa_flutter/core/services/DataService.dart'; + +import '../core/services/DbService.dart'; + import '../core/services/ApiService.dart'; import '../core/services/dialog_service.dart'; import '../core/logger.dart'; @@ -17,8 +21,16 @@ class LocatorInjector { locator.registerLazySingleton(() => ApiService()); _log.d('Initializing Navigator Service'); locator.registerLazySingleton(() => NavigatorService()); - _log.d('Initializing Dialog Service'); + _log.d('Initializing Dialog Service'); locator.registerLazySingleton(() => DialogService()); - + _log.d('Initializing DbService Service'); + locator.registerLazySingleton(() => DbService.instance); + + + // depencies + + + _log.d('Initializing DataService Service'); + locator.registerLazySingleton(() => DataService()); } } \ No newline at end of file diff --git a/lib/core/models/Choice.dart b/lib/core/models/Choice.dart new file mode 100644 index 0000000..9a28e7e --- /dev/null +++ b/lib/core/models/Choice.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; + +class Choice { + const Choice({this.title, this.icon, this.command}); + final String command; + final String title; + final IconData icon; +} \ No newline at end of file diff --git a/lib/core/models/DictDao.dart b/lib/core/models/DictDao.dart new file mode 100644 index 0000000..a54a7f2 --- /dev/null +++ b/lib/core/models/DictDao.dart @@ -0,0 +1,5 @@ +class DictDao { + final int id; + final String name; + DictDao({ this.id, this.name}); +} \ No newline at end of file diff --git a/lib/core/models/Message.dart b/lib/core/models/Message.dart new file mode 100644 index 0000000..0c5aeaf --- /dev/null +++ b/lib/core/models/Message.dart @@ -0,0 +1,8 @@ +class Message { + final String message; + + Message({this.message}); + + + static Message fromJson(Map data) => Message(message : data['message']); +} \ No newline at end of file diff --git a/lib/core/models/ProductDao.dart b/lib/core/models/ProductDao.dart index 5c96465..0cb6bae 100644 --- a/lib/core/models/ProductDao.dart +++ b/lib/core/models/ProductDao.dart @@ -1,7 +1,7 @@ class ProductDao { final String name; - final double price; - int count; + final num price; + num count; ProductDao( {this.name, this.price, this.count}); diff --git a/lib/core/models/Response.dart b/lib/core/models/Response.dart new file mode 100644 index 0000000..01907d9 --- /dev/null +++ b/lib/core/models/Response.dart @@ -0,0 +1,20 @@ +class Response { + final T body; + final int status; + final bool operation; + + Response({this.body, this.operation, this.status}); + + factory Response.fromJson(Map data, Function parser) { + return Response( + body: parser(data['body']), + status: data['status'], + operation: data['operation']); + } + factory Response.fromJsonDynamic(Map data) { + return Response( + body: data['body'], + status: data['status'], + operation: data['operation']); + } +} diff --git a/lib/core/models/session.dart b/lib/core/models/session.dart deleted file mode 100644 index d477415..0000000 --- a/lib/core/models/session.dart +++ /dev/null @@ -1,16 +0,0 @@ -class Session { - String token; - int appCompanyId; - int kassaId; - int userId; - String message; - Session(); - - - Session.fromData(String token ,Map data) - : token = token, - appCompanyId = data['app_company_id'], - userId = data['user_id'], - message = data['message'], - kassaId = data['kassa_id']; -} \ No newline at end of file diff --git a/lib/core/providers.dart b/lib/core/providers.dart index 92bf4ad..3afa3ff 100644 --- a/lib/core/providers.dart +++ b/lib/core/providers.dart @@ -1,3 +1,5 @@ +import 'package:aman_kassa_flutter/core/services/DataService.dart'; +import 'package:aman_kassa_flutter/core/services/DbService.dart'; import 'package:provider/single_child_widget.dart'; import '../core/locator.dart'; @@ -15,16 +17,21 @@ class ProviderInjector { ]; static List _independentServices = [ - Provider.value(value: locator()), - Provider.value(value: locator()), +// Provider.value(value: locator()), +// Provider.value(value: locator()), +// Provider.value(value: locator()), ]; static List _dependentServices = [ - ProxyProvider( - update: (context, api, authenticationService) => - AuthenticationService(api: api), - ) +// ProxyProvider( +// update: (context, api, authenticationService) => +// AuthenticationService(api: api), +// ), +// ProxyProvider2< ApiService ,DbService, DataService>( +// update: (context,api , db, authenticationService) => +// DataService(api: api, db: db), +// ) ]; - + static List _consumableServices = []; -} \ No newline at end of file +} diff --git a/lib/core/services/ApiService.dart b/lib/core/services/ApiService.dart index 0e586d7..ce98c9a 100644 --- a/lib/core/services/ApiService.dart +++ b/lib/core/services/ApiService.dart @@ -2,10 +2,11 @@ import 'dart:convert'; import 'dart:io'; import 'package:aman_kassa_flutter/core/base/base_service.dart'; -import 'package:aman_kassa_flutter/core/models/session.dart'; +import 'package:aman_kassa_flutter/core/entity/Goods.dart'; +import 'package:aman_kassa_flutter/core/models/Message.dart'; +import 'package:aman_kassa_flutter/core/models/Response.dart'; import '../models/authResponse.dart'; - import 'package:http/http.dart' as http; /// The service responsible for networking requests @@ -21,20 +22,25 @@ class ApiService extends BaseService { }; var response = await requestFormData('/authenticate', requestBody); final respStr = await response.stream.bytesToString(); - print(respStr); - AuthResponse aman = AuthResponse.fromJson(json.decode(respStr)); + AuthResponse aman = AuthResponse.fromJson(json.decode(respStr)); return aman.body; } - Future isActive(String token) async { + Future> isActive(String token) async { Map requestBody = {'api_token': token}; var response = await requestFormData('/test_auth', requestBody); final respStr = await response.stream.bytesToString(); - return Session.fromData(token, json.decode(respStr)['body']); + return Response.fromJson(json.decode(respStr), Message.fromJson); } - Future requestFormData( - String point, Map requestBody) async { + Future> logout(String token) async { + Map requestBody = {'api_token': token}; + var response = await requestFormData('/logout', requestBody); + final respStr = await response.stream.bytesToString(); + return Response.fromJson(json.decode(respStr), Message.fromJson); + } + + Future requestFormData(String point, Map requestBody) async { Map headers = { HttpHeaders.contentTypeHeader: "multipart/form-data", HttpHeaders.cacheControlHeader: "no-cache" @@ -48,4 +54,26 @@ class ApiService extends BaseService { return await request.send(); } + Future> getGoodsFromServer(String token) async { + Map requestBody = {'api_token': token}; + var response = await requestFormData('/goods', requestBody); + final respStr = await response.stream.bytesToString(); + print(respStr); + return Response.fromJsonDynamic(json.decode(respStr)); + } + + Future> getCategoryFromServer(String token) async { + Map requestBody = {'api_token': token}; + var response = await requestFormData('/goods_category', requestBody); + final respStr = await response.stream.bytesToString(); + print(respStr); + return Response.fromJsonDynamic(json.decode(respStr)); + } + Future> getServiceFromServer(String token) async { + Map requestBody = {'api_token': token}; + var response = await requestFormData('/services', requestBody); + final respStr = await response.stream.bytesToString(); + print(respStr); + return Response.fromJsonDynamic(json.decode(respStr)); + } } diff --git a/lib/core/services/DataService.dart b/lib/core/services/DataService.dart new file mode 100644 index 0000000..25bd607 --- /dev/null +++ b/lib/core/services/DataService.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:aman_kassa_flutter/core/base/base_service.dart'; +import 'package:aman_kassa_flutter/core/entity/Category.dart'; +import 'package:aman_kassa_flutter/core/entity/Goods.dart'; +import 'package:aman_kassa_flutter/core/entity/Service.dart'; +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/Response.dart'; +import 'package:aman_kassa_flutter/core/services/DbService.dart'; + +import 'ApiService.dart'; + +class DataService extends BaseService { + final ApiService _api = locator(); + final DbService _db = locator(); + + Future> getCategoriesByParentId ( {int parentId} ) async { + List> list = await _db.queryRowsWithWhere(Category_tableName, '$Category_columnParentIn = ?', [parentId ?? 0 ]); + return list.map((e) => Category.fromMap(e)).toList(); + } + + Future> getGoodsByCategoryId ( {int categoryId} ) async { + List> list = await _db.queryRowsWithWhere(Goog_tableName, '$Goog_columnCategoryId = ?', [categoryId ?? 0]); + return list.map((e) => Good.fromMap(e)).toList(); + } + + Future getDataFromServer(String token) async { + try { + + Response goods = await _api.getGoodsFromServer(token); + Response categories = await _api.getCategoryFromServer(token); + Response services = await _api.getServiceFromServer(token); + await _db.deleteAll(Goog_tableName); + await _db.deleteAll(Category_tableName); + await _db.deleteAll(Service_tableName); + log.i('All tables cleaned'); + for (var key in goods.body.keys) { + print(goods.body[key]); + Good row = Good.fromMap(goods.body[key]); + _db.insert(Goog_tableName,row.toMap()); + } + log.i('Inserted ${goods.body.length} to table $Goog_tableName'); + + for (var el in categories.body) { + print(el); + Category row = Category.fromMap(el); + _db.insert(Category_tableName,row.toMap()); + } + log.i('Inserted ${categories.body.length} to table $Category_tableName'); + + for (var key in services.body.keys) { + print(services.body[key]); + Service row = Service.fromMap(services.body[key]); + _db.insert(Service_tableName,row.toMap()); + } + log.i('Inserted ${services.body.length} to table $Service_tableName'); + + return true; + } catch (e) { + print(e); + return false; + } + } +} \ No newline at end of file diff --git a/lib/core/services/DbService.dart b/lib/core/services/DbService.dart new file mode 100644 index 0000000..dde357f --- /dev/null +++ b/lib/core/services/DbService.dart @@ -0,0 +1,129 @@ +import 'dart:io'; + +import 'package:aman_kassa_flutter/core/base/base_service.dart'; +import 'package:aman_kassa_flutter/core/entity/Goods.dart'; +import 'package:aman_kassa_flutter/core/entity/Category.dart'; +import 'package:aman_kassa_flutter/core/entity/Service.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; + +class DbService extends BaseService { + static final _databaseName = "AmanFlutterDb.db"; + static final _databaseVersion = 5; + + // make this a singleton class + DbService._privateConstructor(); + + static final DbService instance = DbService._privateConstructor(); + + // only have a single app-wide reference to the database + static Database _database; + + Future get database async { + if (_database != null) return _database; + // lazily instantiate the db the first time it is accessed + _database = await _initDatabase(); + return _database; + } + + // this opens the database (and creates it if it doesn't exist) + _initDatabase() async { + Directory documentsDirectory = await getApplicationDocumentsDirectory(); + String path = join(documentsDirectory.path, _databaseName); + print(path); + return await openDatabase(path, + version: _databaseVersion, + onUpgrade: _onUpdate, + onCreate: _onCreate + ); + } + Future _onUpdate(Database db, int oldVersion, int newVersion) async { + print('update from $oldVersion to $newVersion'); + //Goods table + await db.execute(''' + DROP TABLE IF EXISTS $Goog_tableName; + DROP TABLE IF EXISTS $Category_tableName; + DROP TABLE IF EXISTS $Service_tableName; + '''); + _onCreate(db, newVersion); + } + + Future _onCreate(Database db, int version) async { + print('create tables'); + //Goods table + await db.execute(''' + CREATE TABLE IF NOT EXISTS $Goog_tableName ( + $Goog_columnId integer primary key unique, + $Goog_columnArticul integer not null, + $Goog_columnName text not null, + $Goog_columnPrice real not null, + $Goog_columnCategoryId integer not null, + $Goog_columnEan text); + '''); + await db.execute(''' + CREATE TABLE IF NOT EXISTS $Category_tableName ( + $Category_columnId integer primary key unique, + $Category_columnName text not null, + $Category_columnParentIn integer); + '''); + await db.execute(''' + CREATE TABLE IF NOT EXISTS $Service_tableName ( + $Service_columnId integer primary key unique, + $Service_columnArticul integer not null, + $Service_columnName text not null, + $Service_columnPrice real not null); + '''); + + } + + // Inserts a row in the database where each key in the Map is a column name + // and the value is the column value. The return value is the id of the + // inserted row. + Future insert(String table, Map row) async { + Database db = await instance.database; + return await db.insert(table, row); + } + + // All of the rows are returned as a list of maps, where each map is + // a key-value list of columns. + Future>> queryAllRows(String table) async { + Database db = await instance.database; + return await db.query(table); + } + + Future>> queryRowsWithWhere(String table, String where, List args ) async { + Database db = await instance.database; + return await db.query(table, where: where, whereArgs: args ); + } + + // All of the methods (insert, query, update, delete) can also be done using + // raw SQL commands. This method uses a raw query to give the row count. + Future queryRowCount(String table) async { + Database db = await instance.database; + return Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM $table')); + } + + // We are assuming here that the id column in the map is set. The other + // column values will be used to update the row. + Future update(String table, Map row) async { + Database db = await instance.database; + int id = row['id']; + return await db.update(table, row, where: 'id = ?', whereArgs: [id]); + } + + // Deletes the row specified by the id. The number of affected rows is + // returned. This should be 1 as long as the row exists. + Future delete(String table, int id) async { + Database db = await instance.database; + return await db.delete(table, where: 'id = ?', whereArgs: [id]); + } + + Future deleteAll(String table) async { + Database db = await instance.database; + return await db.delete(table); + } + + Future close() async => instance.close(); +} diff --git a/lib/core/services/authentication_service.dart b/lib/core/services/authentication_service.dart index c174173..f59073b 100644 --- a/lib/core/services/authentication_service.dart +++ b/lib/core/services/authentication_service.dart @@ -1,5 +1,6 @@ import 'package:aman_kassa_flutter/core/base/base_service.dart'; -import 'package:aman_kassa_flutter/core/models/session.dart'; +import 'package:aman_kassa_flutter/core/models/Message.dart'; +import 'package:aman_kassa_flutter/core/models/Response.dart'; import 'package:aman_kassa_flutter/core/models/user.dart'; import 'package:aman_kassa_flutter/core/models/authResponse.dart'; import 'package:flutter/foundation.dart'; @@ -14,9 +15,6 @@ class AuthenticationService extends BaseService { User _currentUser; User get currentUser => _currentUser; - Session _session; - Session get currentSession => _session; - Future loginWithEmail({ @required String email, @required String password, @@ -34,9 +32,18 @@ class AuthenticationService extends BaseService { } Future isUserLoggedIn(String token) async { - Session session = await _api.isActive(token); - if ("OK" == session.message) { - _session = session; + Response session = await _api.isActive(token); + if ("OK" == session.body.message) { + //_session = session; + return true; + } + return false; + } + + Future logout(String token) async { + Response session = await _api.logout(token); + if ("logout" == session.body.message) { + //_session = session; return true; } return false; diff --git a/lib/main.dart b/lib/main.dart index 0708c01..f7d1394 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,13 @@ //general +import 'package:flutter_redux/flutter_redux.dart'; + import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; //service & tools +import 'package:redux/redux.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; import 'core/locator.dart'; import 'core/providers.dart'; import 'core/router.dart'; @@ -19,34 +23,37 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); //initialize locator await LocatorInjector.setupLocator(); + + await Redux.init(); runApp(MainApplication()); } class MainApplication extends StatelessWidget { + @override Widget build(BuildContext context) { - return MultiProvider( - providers: ProviderInjector.providers.toList(), - child: MaterialApp( - theme: ThemeData( - backgroundColor: backgroundColor, - primaryColor: primaryColor, - accentColor: yellowColor, - scaffoldBackgroundColor: Colors.white, - textTheme: GoogleFonts.latoTextTheme( - Theme.of(context).textTheme, - ) + return StoreProvider( + store: Redux.store, + child: MaterialApp( + theme: ThemeData( + backgroundColor: backgroundColor, + primaryColor: primaryColor, + accentColor: yellowColor, + scaffoldBackgroundColor: Colors.white, + textTheme: GoogleFonts.latoTextTheme( + Theme.of(context).textTheme, + ) + ), + debugShowCheckedModeBanner: false, + builder: (context, child) => Navigator( + key: locator().dialogNavigationKey, + onGenerateRoute: (settings) => MaterialPageRoute( + builder: (context) => DialogManager(child: child)), + ), + navigatorKey: locator().navigatorKey, + home: StartUpView(), // first page + onGenerateRoute: generateRoute, ), - debugShowCheckedModeBanner: false, - builder: (context, child) => Navigator( - key: locator().dialogNavigationKey, - onGenerateRoute: (settings) => MaterialPageRoute( - builder: (context) => DialogManager(child: child)), - ), - navigatorKey: locator().navigatorKey, - home: StartUpView(), // first page - onGenerateRoute: generateRoute, - ), - ); + ); } } \ No newline at end of file diff --git a/lib/redux/actions/main_actions.dart b/lib/redux/actions/main_actions.dart new file mode 100644 index 0000000..d677f2f --- /dev/null +++ b/lib/redux/actions/main_actions.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; + +import 'package:aman_kassa_flutter/core/entity/Category.dart'; +import 'package:aman_kassa_flutter/core/entity/Goods.dart'; +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/DictDao.dart'; +import 'package:aman_kassa_flutter/core/services/ApiService.dart'; +import 'package:aman_kassa_flutter/core/services/DataService.dart'; +import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/redux/state/main_state.dart'; +import 'package:meta/meta.dart'; +import 'package:redux/redux.dart'; +import 'package:redux_thunk/redux_thunk.dart'; + +import '../store.dart'; + +@immutable +class SetMainStateAction { + final MainState mainState; + + SetMainStateAction(this.mainState); +} + +final ApiService _api = locator(); +final DataService _dataService = locator(); +final NavigatorService _navigation = locator(); + + +Future backBottomElement(Store store) async { + List prevCategories = store.state.mainState.prevCategories; + DictDao last = prevCategories.removeLast(); + if(last!=null) { + store.dispatch(SetMainStateAction(MainState(prevCategories: prevCategories))); + store.dispatch(selectBottomElement(last.id)); + } + +} + +ThunkAction selectBottomElement(int parentId) { + return (Store store) async { + store.dispatch(SetMainStateAction(MainState(bottomSheetLoading: true))); + try { + List prevCategories = store.state.mainState.prevCategories; + if(parentId == 0) { + prevCategories = []; + } + store.state.mainState.bottomSheetElements.forEach((element) { + if(element is Category && element.id == parentId){ + prevCategories.add(DictDao(id: element.parentIn, name: element.name)); + } + }); + List categories = await _dataService.getCategoriesByParentId(parentId: parentId); + List goods = await _dataService.getGoodsByCategoryId(categoryId: parentId); + List _bottomSheetElements = []; + _bottomSheetElements.addAll(categories); + _bottomSheetElements.addAll(goods); + store.dispatch( + SetMainStateAction( + MainState( + bottomSheetLoading: false, + bottomSheetElements: _bottomSheetElements, + prevCategories: prevCategories + ) + ) + ); + } catch (e) { + print(e); + store.dispatch(SetMainStateAction(MainState(bottomSheetLoading: false))); + } + }; +} diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart new file mode 100644 index 0000000..3df788e --- /dev/null +++ b/lib/redux/actions/user_actions.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; + +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/Message.dart'; +import 'package:aman_kassa_flutter/core/models/authResponse.dart'; +import 'package:aman_kassa_flutter/core/models/Response.dart'; +import 'package:aman_kassa_flutter/core/models/user.dart'; +import 'package:aman_kassa_flutter/core/route_names.dart'; +import 'package:aman_kassa_flutter/core/services/ApiService.dart'; +import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; +import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/redux/state/user_state.dart'; +import 'package:redux/redux.dart'; +import 'package:meta/meta.dart'; +import 'package:redux_thunk/redux_thunk.dart'; +import '../store.dart'; + +@immutable +class SetUserStateAction { + final UserState userState; + SetUserStateAction(this.userState); +} +final ApiService _api = locator(); +final NavigatorService _navigation = locator(); +final DialogService _dialogService = locator(); + +Future checkUserAction(Store store) async { + store.dispatch(SetUserStateAction(UserState(isLoading: true))); + try { + Response session = await _api.isActive('test'); + bool isAuthenticated = "OK" == session.body.message; + store.dispatch( + SetUserStateAction( + UserState( + isLoading: false, + isAuthenticated: isAuthenticated, + ), + ), + ); + + if(!isAuthenticated){ + _navigation.replace(LoginViewRoute); + } + + + } catch (error) { + store.dispatch(SetUserStateAction(UserState(isLoading: false))); + } +} + + +ThunkAction authenticate(String email, String password) { + return (Store store) async { + store.dispatch(SetUserStateAction(UserState(isLoading: true))); + try { + AuthBody result = await _api.authenticate(email, password); + store.dispatch(SetUserStateAction(UserState( + isLoading: false, + loginFormMessage: LoginFormMessage(email: result.email?.join(","), password: result.password?.join(","), message: result.message), + user: result.user + ))); + if(result.user == null && result.message!=null){ + _dialogService.showDialog(title: 'Warning', buttonTitle: 'Ok', description: result.message); + } + if(result.user!=null) { + _navigation.replace(HomeViewRoute); + } + } catch(e) { + store.dispatch(SetUserStateAction(UserState(isLoading: false))); + } + }; +} diff --git a/lib/redux/reducers/main_reducer.dart b/lib/redux/reducers/main_reducer.dart new file mode 100644 index 0000000..b9171f9 --- /dev/null +++ b/lib/redux/reducers/main_reducer.dart @@ -0,0 +1,11 @@ +import 'package:aman_kassa_flutter/redux/actions/main_actions.dart'; +import 'package:aman_kassa_flutter/redux/state/main_state.dart'; + +mainReducer(MainState prevState, SetMainStateAction action) { + final payload = action.mainState; + return prevState.copyWith( + bottomSheetElements: payload.bottomSheetElements, + bottomSheetLoading: payload.bottomSheetLoading, + prevCategories: payload.prevCategories, + ); +} diff --git a/lib/redux/reducers/user_reducer.dart b/lib/redux/reducers/user_reducer.dart new file mode 100644 index 0000000..4ffa940 --- /dev/null +++ b/lib/redux/reducers/user_reducer.dart @@ -0,0 +1,14 @@ +import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; +import 'package:aman_kassa_flutter/redux/state/user_state.dart'; + + +userReducer(UserState prevState, SetUserStateAction action) { + final payload = action.userState; + return prevState.copyWith( + isError: payload.isError, + isLoading: payload.isLoading, + user: payload.user, + isAuthenticated: payload.isAuthenticated, + loginFormMessage: payload.loginFormMessage, + ); +} \ No newline at end of file diff --git a/lib/redux/state/main_state.dart b/lib/redux/state/main_state.dart new file mode 100644 index 0000000..88bf17c --- /dev/null +++ b/lib/redux/state/main_state.dart @@ -0,0 +1,30 @@ +import 'package:aman_kassa_flutter/core/models/DictDao.dart'; +import 'package:aman_kassa_flutter/core/models/user.dart'; +import 'package:meta/meta.dart'; + +@immutable +class MainState { + final List bottomSheetElements; + final bool bottomSheetLoading; + final List prevCategories; + + MainState({this.bottomSheetElements, this.bottomSheetLoading, this.prevCategories}); + + factory MainState.initial() => MainState( + bottomSheetElements: [], + bottomSheetLoading: false, + prevCategories: [], + ); + + MainState copyWith({ + @required bottomSheetElements, + @required bottomSheetLoading, + @required prevCategories, + }) { + return MainState( + bottomSheetElements: bottomSheetElements ?? this.bottomSheetElements, + bottomSheetLoading: bottomSheetLoading ?? this.bottomSheetLoading, + prevCategories: prevCategories ?? this.prevCategories, + ); + } +} diff --git a/lib/redux/state/user_state.dart b/lib/redux/state/user_state.dart new file mode 100644 index 0000000..c2d6f3d --- /dev/null +++ b/lib/redux/state/user_state.dart @@ -0,0 +1,49 @@ +import 'package:aman_kassa_flutter/core/models/user.dart'; +import 'package:meta/meta.dart'; + +@immutable +class UserState { + final bool isError; + final bool isLoading; + final bool isAuthenticated; + final LoginFormMessage loginFormMessage; + final User user; + + UserState({ + this.isError, + this.isLoading, + this.isAuthenticated, + this.user, + this.loginFormMessage + }); + + factory UserState.initial() => UserState( + isLoading: false, + isError: false, + isAuthenticated: false, + loginFormMessage: LoginFormMessage(), + ); + + UserState copyWith({ + @required bool isError, + @required bool isLoading, + @required User user, + @required bool isAuthenticated, + @required LoginFormMessage loginFormMessage, + }) { + return UserState( + isError: isError ?? this.isError, + isLoading: isLoading ?? this.isLoading, + isAuthenticated: isAuthenticated ?? this.isAuthenticated, + user: user ?? this.user, + loginFormMessage: loginFormMessage ?? this.loginFormMessage, + ); + } +} + +class LoginFormMessage { + final String email; + final String password; + final String message; + LoginFormMessage({this.email, this.password, this.message}); +} \ No newline at end of file diff --git a/lib/redux/store.dart b/lib/redux/store.dart new file mode 100644 index 0000000..29ef873 --- /dev/null +++ b/lib/redux/store.dart @@ -0,0 +1,69 @@ +import 'package:aman_kassa_flutter/redux/actions/main_actions.dart'; +import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; +import 'package:aman_kassa_flutter/redux/reducers/main_reducer.dart'; +import 'package:aman_kassa_flutter/redux/reducers/user_reducer.dart'; +import 'package:aman_kassa_flutter/redux/state/main_state.dart'; +import 'package:aman_kassa_flutter/redux/state/user_state.dart'; +import 'package:meta/meta.dart'; +import 'package:redux/redux.dart'; +import 'package:redux_thunk/redux_thunk.dart'; + + +//reducer context +AppState appReducer(AppState state, dynamic action) { + + if (action is SetUserStateAction) { /** UserAction **/ + final nextUserState = userReducer(state.userState, action); + return state.copyWith(userState: nextUserState); + } else if(action is SetMainStateAction) { /** MainAction **/ + final nextMainState = mainReducer(state.mainState, action); + return state.copyWith(mainState: nextMainState); + } + return state; +} + +//Main State +@immutable +class AppState { + final UserState userState; + final MainState mainState; + AppState({ + @required this.userState, + @required this.mainState, + }); + + //stable work + AppState copyWith({ + UserState userState, + MainState mainState, + }) { + return AppState( + userState: userState ?? this.userState, + mainState: mainState ?? this.mainState, + ); + } +} + +class Redux { + static Store _store; + + static Store get store { + if (_store == null) { + throw Exception("store is not initialized"); + } else { + return _store; + } + } + + //initial context + static Future init() async { + final userStateInitial = UserState.initial(); + final mainStateInitial = MainState.initial(); + + _store = Store( + appReducer, + middleware: [thunkMiddleware], + initialState: AppState(userState: userStateInitial, mainState: mainStateInitial), + ); + } +} \ No newline at end of file diff --git a/lib/shared/shared_styles.dart b/lib/shared/shared_styles.dart index 2bbf659..d91a225 100644 --- a/lib/shared/shared_styles.dart +++ b/lib/shared/shared_styles.dart @@ -20,8 +20,8 @@ const EdgeInsets largeFieldPadding = const EdgeInsets.symmetric(horizontal: 15, vertical: 15); // Text Variables +const TextStyle productTextStyle = const TextStyle(fontWeight: FontWeight.w400, color: Colors.black, fontSize: 15); const TextStyle buttonTitleTextStyle = const TextStyle(fontWeight: FontWeight.w400, color: whiteColor, fontSize: 15); - const TextStyle buttonBigTitleTextStyle = const TextStyle(fontWeight: FontWeight.w400, color: whiteColor, fontSize: 22, ); const TextStyle stepTitleTextStyle = diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart index 310dce7..1732107 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -1,124 +1,135 @@ -library home_view; - +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/Choice.dart'; +import 'package:aman_kassa_flutter/core/services/DataService.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart'; -import 'package:aman_kassa_flutter/shared/shared_styles.dart'; + import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:provider/provider.dart'; -import 'package:stacked/stacked.dart'; -import 'package:aman_kassa_flutter/core/models/ProductDao.dart'; -import 'package:aman_kassa_flutter/widgets/components/ProductListItem.dart'; -import 'home_view_model.dart'; -import '../../widgets/components/calculator/calculator-buttons.dart'; -import '../../widgets/components/calculator/number-display.dart'; -part './tabs/KassaTab.dart'; -part './tabs/CalculatorTab.dart'; -part './tabs/AdditionalTab.dart'; +import './tabs/KassaTab.dart'; +import './tabs/AdditionalTab.dart'; +import './tabs/CalculatorTab.dart'; -class Choice { - const Choice({this.title, this.icon}); - final String title; - final IconData icon; -} const List choices = const [ - const Choice(title: 'Обновить номенклатуру', icon: Icons.directions_car), - const Choice(title: 'Помощь', icon: Icons.directions_car), - const Choice(title: 'О Программе', icon: Icons.directions_car), - const Choice(title: 'Язык', icon: Icons.directions_car), - const Choice(title: 'Выйти', icon: Icons.directions_bike) + const Choice(title: 'Обновить номенклатуру', icon: Icons.update, command: 'update'), + const Choice(title: 'Помощь', icon: Icons.help, command: 'help'), + const Choice(title: 'О Программе', icon: Icons.info_outline, command: 'info'), + const Choice(title: 'Язык', icon: Icons.language, command: 'language'), + const Choice(title: 'Выйти', icon: Icons.exit_to_app, command: 'exit') ]; -class HomeView extends StatelessWidget { +class HomeView extends StatefulWidget { + @override + _HomeViewState createState() => _HomeViewState(); +} + +class _HomeViewState extends State { + PageController pageController; + int selectedTabIndex; + DataService _dataService = locator(); + + + @override + void initState() { + // TODO: implement initState + super.initState(); + selectedTabIndex = 0; + pageController = new PageController(initialPage: selectedTabIndex); + } + + void _onSelectChoice(Choice choice) async { + if(choice.command == 'exit') { + + } else if (choice.command == 'update') { + bool result = await _dataService.getDataFromServer(Redux.store.state.userState.user.token); + print('result: $result'); + } + } + @override Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - viewModelBuilder: () => HomeViewModel( - authenticationService: Provider.of(context), - navigationService: Provider.of(context)), - onModelReady: (viewModel) { - viewModel.initialize(); - viewModel.pageController = - new PageController(initialPage: viewModel.tabIndex); - }, - builder: (context, viewModel, child) { - return Scaffold( - appBar: AppBar( - title: Center( - child: Container( - width: 100, - child: Image( - image: AssetImage('assets/images/logo.png'), - fit: BoxFit.fitHeight, - ), - )), - actions: [ - PopupMenuButton( - icon: Icon( - Icons.more_vert, - color: menuColor, - ), - onSelected: (s) {}, - itemBuilder: (BuildContext context) { - return choices.map((Choice choice) { - return PopupMenuItem( - value: choice, - child: Text(choice.title), - ); - }).toList(); - }, - ) - ], - backgroundColor: fillColor, + return Scaffold( + appBar: AppBar( + title: Center( + child: Container( + width: 100, + child: Image( + image: AssetImage('assets/images/logo.png'), + fit: BoxFit.fitHeight, + ), + )), + actions: [ + PopupMenuButton( + icon: Icon( + Icons.more_vert, + color: menuColor, ), - body: PageView( - onPageChanged: (index) { - viewModel.tabIndex = index; - }, - controller: viewModel.pageController, - children: [ - KassaTab(viewModel, 0), - CalculatorTab(viewModel, 1), - AdditionalTab(viewModel, 2) - ], - ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: viewModel.tabIndex, - backgroundColor: menuColor, - type: BottomNavigationBarType.shifting, - items: [ - BottomNavigationBarItem( - backgroundColor: menuColor, - icon: Icon(MdiIcons.cashRegister, color: Colors.white), - title: new Text( - 'Касса', - style: TextStyle(color: Colors.white), - )), - BottomNavigationBarItem( - icon: Icon(MdiIcons.calculator, color: Colors.white), - title: new Text( - 'Калькулятор', - style: TextStyle(color: Colors.white), - )), - BottomNavigationBarItem( - icon: Icon(MdiIcons.tune, color: Colors.white), - title: new Text( - 'Опции', - style: TextStyle(color: Colors.white), - )), - ], - onTap: (index) { - viewModel.pageController.animateToPage( - index, - duration: const Duration(milliseconds: 300), - curve: Curves.easeIn, + onSelected: _onSelectChoice, + itemBuilder: (BuildContext context) { + return choices.map((Choice choice) { + return PopupMenuItem( + value: choice, + child: Row(children: [ + Icon(choice.icon, color: primaryColor,), + Text(choice.title) + ], ), ); - }, - ), + }).toList(); + }, + ) + ], + backgroundColor: fillColor, + ), + body: PageView( + onPageChanged: (index) { + setState(() { + selectedTabIndex = index; + }); + }, + controller: pageController, + children: [ + KassaTab(0), + CalculatorTab(1), + AdditionalTab(2), + ], + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: selectedTabIndex, + backgroundColor: menuColor, + type: BottomNavigationBarType.shifting, + items: [ + BottomNavigationBarItem( + backgroundColor: menuColor, + icon: Icon(MdiIcons.cashRegister, color: Colors.white), + title: new Text( + 'Касса', + style: TextStyle(color: Colors.white), + )), + BottomNavigationBarItem( + icon: Icon(MdiIcons.calculator, color: Colors.white), + title: new Text( + 'Калькулятор', + style: TextStyle(color: Colors.white), + )), + BottomNavigationBarItem( + icon: Icon(MdiIcons.tune, color: Colors.white), + title: new Text( + 'Опции', + style: TextStyle(color: Colors.white), + )), + ], + onTap: (index) { + pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, ); - }); + }, + ), + ); } } diff --git a/lib/views/home/home_view_model.dart b/lib/views/home/home_view_model.dart index 9d700df..b3a0313 100644 --- a/lib/views/home/home_view_model.dart +++ b/lib/views/home/home_view_model.dart @@ -1,6 +1,10 @@ import 'package:aman_kassa_flutter/core/base/base_view_model.dart'; +import 'package:aman_kassa_flutter/core/entity/Category.dart'; import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/Choice.dart'; import 'package:aman_kassa_flutter/core/models/user.dart'; +import 'package:aman_kassa_flutter/core/route_names.dart'; +import 'package:aman_kassa_flutter/core/services/DataService.dart'; import 'package:aman_kassa_flutter/core/services/authentication_service.dart'; import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; @@ -10,12 +14,15 @@ import 'package:flutter/material.dart'; class HomeViewModel extends BaseViewModel { NavigatorService _navigationService; AuthenticationService _authenticationService; + DataService _dataService; final DialogService _dialogService = locator(); HomeViewModel({ @required AuthenticationService authenticationService, @required NavigatorService navigationService, + @required DataService dataService, }) : _authenticationService = authenticationService, + _dataService = dataService, _navigationService = navigationService; User _currentUser; @@ -28,13 +35,48 @@ class HomeViewModel extends BaseViewModel { notifyListeners(); } + void onSelected(Choice choice) async { + log.i(choice.command); + if(choice.command == 'exit') { + bool result = await _authenticationService.logout(_currentUser.token); + if (result) { + _navigationService.replace(LoginViewRoute); + } + } else if (choice.command == 'update') { + log.i(_authenticationService.currentUser.token); + bool result = await _dataService.getDataFromServer(_authenticationService.currentUser.token); + log.i(result); + } + } + PageController get pageController => this._pageController; PageController _pageController; set pageController(PageController pageController) { this._pageController = pageController; } + List _bottomSheetElements = []; + List get bottomSheetElements => this._bottomSheetElements; + int _parentId; + void selectBottomElement(int parentId) async { +// log.i('parentId=$parentId'); +// _parentId = parentId; +// _bottomSheetElements.clear(); +// List categories = await _dataService.getCategoriesByParentId(parentId: parentId); +// List goods = await _dataService.getGoodsByCategoryId(categoryId: parentId); +// _bottomSheetElements.addAll(categories); +// _bottomSheetElements.addAll(goods); +// notifyListeners(); + } + + + + String _text; + get text => this._text; + set text(String text) => this._text = text; + void initialize() { + _currentUser = _authenticationService.currentUser; } diff --git a/lib/views/home/tabs/AdditionalTab.dart b/lib/views/home/tabs/AdditionalTab.dart index 70cdab8..0066a51 100644 --- a/lib/views/home/tabs/AdditionalTab.dart +++ b/lib/views/home/tabs/AdditionalTab.dart @@ -1,20 +1,28 @@ -part of home_view; +import 'package:aman_kassa_flutter/redux/state/user_state.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:flutter/material.dart'; +import 'package:aman_kassa_flutter/views/home/home_view_model.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:google_fonts/google_fonts.dart'; class AdditionalTab extends StatelessWidget { - final HomeViewModel viewModel; final int index; - - AdditionalTab(this.viewModel, this.index); - + AdditionalTab(this.index); @override Widget build(BuildContext context) { - return Container( - child: Column( - children: [ - Text('AdditionalTab index:$index ${viewModel.currentUser.fullName} ', - style: GoogleFonts.lato(color: Colors.black87)) - ], - ), + return StoreConnector( + converter: (store) => store.state.userState, + builder: (context, vm) { + return Container( + child: Column( + children: [ + Text('AdditionalTab index:$index ${vm.user.fullName} ', + style: GoogleFonts.lato(color: Colors.black87)) + ], + ), + ); + }, + ); } } \ No newline at end of file diff --git a/lib/views/home/tabs/CalculatorTab.dart b/lib/views/home/tabs/CalculatorTab.dart index 7c6dcfb..c051ca1 100644 --- a/lib/views/home/tabs/CalculatorTab.dart +++ b/lib/views/home/tabs/CalculatorTab.dart @@ -1,10 +1,14 @@ -part of home_view; +import 'package:flutter/material.dart'; +import 'package:aman_kassa_flutter/views/home/home_view_model.dart'; +import 'package:aman_kassa_flutter/widgets/components/calculator/calculator-buttons.dart'; +import 'package:aman_kassa_flutter/widgets/components/calculator/number-display.dart'; + + class CalculatorTab extends StatelessWidget { - final HomeViewModel viewModel; final int index; - CalculatorTab(this.viewModel, this.index); + CalculatorTab(this.index); @override Widget build(BuildContext context) { diff --git a/lib/views/home/tabs/KassaTab.dart b/lib/views/home/tabs/KassaTab.dart index 6d6195f..cb2dfa5 100644 --- a/lib/views/home/tabs/KassaTab.dart +++ b/lib/views/home/tabs/KassaTab.dart @@ -1,4 +1,14 @@ -part of home_view; + +import 'package:aman_kassa_flutter/core/models/ProductDao.dart'; +import 'package:aman_kassa_flutter/redux/actions/main_actions.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:aman_kassa_flutter/shared/app_colors.dart'; +import 'package:aman_kassa_flutter/shared/shared_styles.dart'; +import 'package:aman_kassa_flutter/views/home/tabs/kassaView/CatalogBottomSheet.dart'; +import 'package:aman_kassa_flutter/views/home/tabs/kassaView/ProductAddBottomSheet.dart'; +import 'package:aman_kassa_flutter/widgets/components/ProductListItem.dart'; +import 'package:flutter/material.dart'; + List litems = [ "1", @@ -13,33 +23,14 @@ List litems = [ "2", "3", "4", - "1", - "2", - "3", - "4", - "1", - "2", - "3", - "4", - "1", - "2", - "3", - "4", - "1", - "2", - "3", - "4", - "1", - "2", - "3", - "4" + "1" ]; class KassaTab extends StatelessWidget { - final HomeViewModel viewModel; + final int index; - KassaTab(this.viewModel, this.index); + KassaTab(this.index); Widget buildItem(BuildContext ctxt, int index) { return ProductListItem( @@ -49,87 +40,108 @@ class KassaTab extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: fillColor, - body: Padding( - padding: EdgeInsets.all(4), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: RaisedButton( - padding: EdgeInsets.all(8), - color: primaryColor, - child: Text( - "Добавить", - style: buttonBigTitleTextStyle, - ), - onPressed: () => null, + return Padding( + padding: EdgeInsets.all(4), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: RaisedButton( + padding: EdgeInsets.all(8), + color: primaryColor, + child: Text( + "Добавить", + style: buttonBigTitleTextStyle, ), + onPressed: () { showModalBottomSheetCatalog(context, 'add');}, ), ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: RaisedButton( - padding: EdgeInsets.all(8), - color: greenColor, - child: Text( - "Каталог", - style: buttonBigTitleTextStyle, - ), - onPressed: () => null, - ), - ), - ), - ], - ), - Expanded( - child: Container( - child: ListView.builder( - itemCount: litems.length, - itemBuilder: (BuildContext ctxt, int index) => - buildItem(ctxt, index)), ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: RaisedButton( + padding: EdgeInsets.all(8), + color: greenColor, + child: Text( + "Каталог", + style: buttonBigTitleTextStyle, + ), + onPressed: () { showModalBottomSheetCatalog(context, 'catalog');}, + ), + ), + ), + ], + ), + Expanded( + child: Container( + child: ListView.builder( + itemCount: litems.length, + itemBuilder: (BuildContext ctxt, int index) => + buildItem(ctxt, index)), ), - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: RaisedButton( - padding: EdgeInsets.all(8), - color: redColor, - child: Text( - "Возврат", - style: buttonBigTitleTextStyle, - ), - onPressed: () => null, + ), + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: RaisedButton( + padding: EdgeInsets.all(8), + color: redColor, + child: Text( + "Возврат", + style: buttonBigTitleTextStyle, ), + onPressed: () => null, ), ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: RaisedButton( - padding: EdgeInsets.all(8), - color: greenColor, - child: Text( - "Оплата", - style: buttonBigTitleTextStyle, - ), - onPressed: () => null, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: RaisedButton( + padding: EdgeInsets.all(8), + color: greenColor, + child: Text( + "Оплата", + style: buttonBigTitleTextStyle, ), + onPressed: () => null, ), ), - ], - ) - ], - ), + ), + ], + ) + ], ), ); } + + void showModalBottomSheetCatalog(BuildContext context, String action) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context){ + return DraggableScrollableSheet( + initialChildSize: 0.8, + maxChildSize: 0.95, + minChildSize: 0.5, + builder: (BuildContext context, ScrollController scrollController) { + if( action == 'add') { + return ProductAddBottomSheet(); + } else { + return CatalogBottomSheet(scrollController: scrollController,); + } + }, + ); + } + ); + } + + } diff --git a/lib/views/home/tabs/kassaView/CatalogBottomSheet.dart b/lib/views/home/tabs/kassaView/CatalogBottomSheet.dart new file mode 100644 index 0000000..34f8e90 --- /dev/null +++ b/lib/views/home/tabs/kassaView/CatalogBottomSheet.dart @@ -0,0 +1,104 @@ +import 'package:aman_kassa_flutter/core/entity/Category.dart'; +import 'package:aman_kassa_flutter/core/entity/Goods.dart'; +import 'package:aman_kassa_flutter/redux/actions/main_actions.dart'; +import 'package:aman_kassa_flutter/redux/state/main_state.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:aman_kassa_flutter/shared/app_colors.dart'; +import 'package:aman_kassa_flutter/shared/shared_styles.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; + +class CatalogBottomSheet extends StatelessWidget { + final ScrollController scrollController; + + CatalogBottomSheet({this.scrollController}); + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: (store) => store.state.mainState, + onInit: (store) => Redux.store.dispatch(selectBottomElement(0)), + builder: (context, vm) { + return WillPopScope( + onWillPop: () { + if (vm.prevCategories.length > 0) { + Redux.store.dispatch(backBottomElement); + } else + Navigator.pop(context); + return new Future(() => false); + }, + child: Scaffold( + appBar: AppBar( + title: Text( + vm.prevCategories.isNotEmpty + ? vm.prevCategories?.last?.name + : '', + style: TextStyle(color: Colors.black45), + ), + iconTheme: IconThemeData(color: Colors.black), + backgroundColor: whiteColor, + elevation: 3, + leading: IconButton( + icon: Icon(vm.prevCategories.length > 0 + ? Icons.arrow_back + : Icons.close), + onPressed: () { + if (vm.prevCategories.length > 0) { + Redux.store.dispatch(backBottomElement); + } else + Navigator.pop(context); + }, + ), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ListView.builder( + controller: scrollController, + itemCount: vm.bottomSheetElements.length, + itemBuilder: (context, index) { + var el = vm.bottomSheetElements[index]; + String name; + if (el is Category) { + Category category = el; + name = category.name; + } else if (el is Good) { + Good good = el; + name = good.name; + } + return Card( + child: ListTile( + leading: Icon( + el is Category ? Icons.layers : Icons.move_to_inbox, + size: 40, + ), + title: Text(name), + onTap: () { + if (el is Category) { + Redux.store.dispatch(selectBottomElement(el.id)); + } + }, + trailing: + el is Category ? Icon(Icons.chevron_right) : null, + ), + ); + }, + ), + ), + RaisedButton( + child: Text( + 'Отмена', + style: buttonBigTitleTextStyle, + ), + color: redColor, + onPressed: () { + Navigator.pop(context); + }), + ], + )), + ); + }, + ); + } +} diff --git a/lib/views/home/tabs/kassaView/ProductAddBottomSheet.dart b/lib/views/home/tabs/kassaView/ProductAddBottomSheet.dart new file mode 100644 index 0000000..c8e1591 --- /dev/null +++ b/lib/views/home/tabs/kassaView/ProductAddBottomSheet.dart @@ -0,0 +1,14 @@ +import 'package:aman_kassa_flutter/shared/app_colors.dart'; +import 'package:flutter/material.dart'; + +class ProductAddBottomSheet extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: whiteColor + ), + child: Text('ProductAddBottomSheet'), + ); + } +} diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart index 8b05d38..2b4b254 100644 --- a/lib/views/login/login_view.dart +++ b/lib/views/login/login_view.dart @@ -1,15 +1,13 @@ -library login_view; - +import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; +import 'package:aman_kassa_flutter/redux/state/user_state.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; import 'package:aman_kassa_flutter/widgets/fields/busy_button.dart'; import 'package:aman_kassa_flutter/widgets/fields/input_field.dart'; import 'package:aman_kassa_flutter/widgets/fields/text_link.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:stacked/stacked.dart'; -import 'login_view_model.dart'; class LoginView extends StatelessWidget { final emailController = TextEditingController(text: 'test@kkm-kassa.kz'); @@ -18,30 +16,16 @@ class LoginView extends StatelessWidget { final FocusNode passwordNode = new FocusNode(); final GlobalKey _scaffoldKey = new GlobalKey(); + _pressBtnEnter() async { + Redux.store.dispatch(authenticate(emailController.text, passwordController.text)); + } + @override Widget build(BuildContext context) { - login(LoginViewModel viewModel) async { - String message = await viewModel.login( - email: emailController.text.trim(), - password: passwordController.text); - if (message != null) { - _scaffoldKey.currentState.showSnackBar(new SnackBar( - content: new Text( - message, - style: GoogleFonts.lato(), - ))); - } - } - - //LoginViewModel viewModel = LoginViewModel(); - return ViewModelBuilder.reactive( - viewModelBuilder: () => LoginViewModel( - authenticationService: Provider.of(context), - navigationService: Provider.of(context)), - onModelReady: (viewModel) { - // Do something once your viewModel is initialized - }, - builder: (context, viewModel, child) { + return StoreConnector( + converter: (store) => store.state.userState, + builder: (context, vm) { + print('build'); return Scaffold( key: _scaffoldKey, backgroundColor: fillColor, @@ -62,17 +46,16 @@ class LoginView extends StatelessWidget { controller: emailController, textInputType: TextInputType.emailAddress, nextFocusNode: passwordNode, - additionalNote: viewModel.messageEmail, + additionalNote: vm.loginFormMessage.email, ), verticalSpaceSmall, InputField( - placeholder: 'Пароль', - password: true, - controller: passwordController, - fieldFocusNode: passwordNode, - additionalNote: viewModel.messagePassword, - enterPressed: () => login(viewModel), - ), + placeholder: 'Пароль', + password: true, + controller: passwordController, + fieldFocusNode: passwordNode, + additionalNote: vm.loginFormMessage.password, + enterPressed: _pressBtnEnter), verticalSpaceMedium, Row( mainAxisSize: MainAxisSize.max, @@ -80,8 +63,8 @@ class LoginView extends StatelessWidget { children: [ BusyButton( title: 'Войти', - busy: viewModel.busy, - onPressed: () => login(viewModel), + busy: vm.isLoading, + onPressed: _pressBtnEnter, ) ], ), @@ -95,4 +78,6 @@ class LoginView extends StatelessWidget { )); }); } + + } diff --git a/lib/views/login/login_view_model.dart b/lib/views/login/login_view_model.dart deleted file mode 100644 index 6010e30..0000000 --- a/lib/views/login/login_view_model.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:aman_kassa_flutter/core/base/base_view_model.dart'; -import 'package:aman_kassa_flutter/core/route_names.dart'; -import 'package:aman_kassa_flutter/core/services/authentication_service.dart'; -import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; -import 'package:flutter/material.dart'; -import 'package:aman_kassa_flutter/core/models/authResponse.dart'; - -class LoginViewModel extends BaseViewModel { - - //service - NavigatorService _navigationService; - AuthenticationService _authenticationService; - - //private var - String _messageEmail; - String _messagePassword; - - //public getter setter - get messageEmail => this._messageEmail; - set messageEmail(String message) { - this._messageEmail = message; - notifyListeners(); - } - get messagePassword => this._messagePassword; - set messagePassword(String message) { - this._messagePassword = message; - notifyListeners(); - } - - - LoginViewModel({ - @required AuthenticationService authenticationService, - @required NavigatorService navigationService, - }) : - _authenticationService = authenticationService , - _navigationService = navigationService; - - - Future login({String email, String password}) async { - String result; - busy = true; - try { - AuthBody response = await _authenticationService.loginWithEmail(email: email, password: password); - - if(response!=null) { - if(response.user != null){ - _navigationService.replace(HomeViewRoute); - } else { - // show error on validate - if(response.email?.isEmpty == false){ - messageEmail = response.email.join(","); - } else { - messageEmail = null; - } - if(response.password?.isEmpty == false){ - messagePassword = response.password.join(","); - } else { - messagePassword = null; - } - //return meesage on snackbar for user screen - if(response.message!=null) { - result = response.message; - } - } - } - - } finally { - busy = false ; - } - return result; - } - - // Add ViewModel specific code here -} \ No newline at end of file diff --git a/lib/views/start_up/start_up_view.dart b/lib/views/start_up/start_up_view.dart index 697614a..cc9b1f4 100644 --- a/lib/views/start_up/start_up_view.dart +++ b/lib/views/start_up/start_up_view.dart @@ -1,20 +1,29 @@ -library home_view; - -import 'package:provider/provider.dart'; - -import 'start_up_view_model.dart'; +import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; +import 'package:aman_kassa_flutter/redux/state/user_state.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -class StartUpView extends StatelessWidget { + +class StartUpView extends StatefulWidget { + @override + _StartUpViewState createState() => _StartUpViewState(); +} + +class _StartUpViewState extends State { + + @override + void initState() { + // TODO: implement initState + super.initState(); + Redux.store.dispatch(checkUserAction); + } + @override Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - viewModelBuilder: () => StartUpViewModel(authenticationService: Provider.of(context), navigationService: Provider.of(context)), - onModelReady: (viewModel) { - viewModel.handleStartUpLogic(); - }, - builder: (context, viewModel, child) { + return StoreConnector( + converter: (store) => store.state.userState, + builder: (context, userState) { return Scaffold( body: Center( child: Column( @@ -25,6 +34,8 @@ class StartUpView extends StatelessWidget { height: 200, child: Image.asset('assets/images/icon_large.png'), ), + Text('${userState.isLoading}'), + Text('${userState.isAuthenticated}'), CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation( diff --git a/lib/views/start_up/start_up_view_model.dart b/lib/views/start_up/start_up_view_model.dart deleted file mode 100644 index 993c415..0000000 --- a/lib/views/start_up/start_up_view_model.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:aman_kassa_flutter/core/base/base_view_model.dart'; -import 'package:aman_kassa_flutter/core/route_names.dart'; -import 'package:aman_kassa_flutter/core/services/authentication_service.dart'; -import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; -import 'package:flutter/material.dart'; - -class StartUpViewModel extends BaseViewModel { - NavigatorService _navigationService; - AuthenticationService _authenticationService; - - StartUpViewModel({ - @required AuthenticationService authenticationService, - @required NavigatorService navigationService, - }) : - _authenticationService = authenticationService , - _navigationService = navigationService; - - - - Future handleStartUpLogic() async { - // Register for push notifications - //await _pushNotificationService.initialise(); - - var hasLoggedInUser = await _authenticationService.isUserLoggedIn('test'); - log.i('hasLoggedInUser $hasLoggedInUser'); - if (hasLoggedInUser) { - //_navigationService.navigateTo(HomeViewRoute); - _navigationService.replace(HomeViewRoute); - } else { - _navigationService.replace(LoginViewRoute); - } - } -} \ No newline at end of file diff --git a/lib/widgets/components/ProductListItem.dart b/lib/widgets/components/ProductListItem.dart index cc4d1b3..9b52ca8 100644 --- a/lib/widgets/components/ProductListItem.dart +++ b/lib/widgets/components/ProductListItem.dart @@ -1,57 +1,81 @@ +import 'package:aman_kassa_flutter/shared/shared_styles.dart'; import 'package:flutter/material.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/core/models/ProductDao.dart'; class ProductListItem extends StatelessWidget { - ProductDao item; - ProductListItem({ - this.item - }); + ProductListItem({this.item}); @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4), + return Padding( + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 4), child: Column( children: [ - Container( - child: Row( - children: [ - Expanded( - child: Container( - color: whiteColor, - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4), - child: Text(item.name) - ), - ), - Expanded( - child: Container( - color: whiteColor, - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4), - child: Text('${item.price?.toString()} x ${item.count?.toString()}', textAlign: TextAlign.right) - ), - ) - ], - ), - ), Row( children: [ Expanded( child: Container( - color: whiteColor, - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4), - child: Text(item.name) - ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + child: Text(item.name, style: productTextStyle,)), ), Expanded( - child: Container(child: Text(item.name)), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + child: Text( + '${item.price?.toString()} x ${item.count?.toString()}', + textAlign: TextAlign.right, style: productTextStyle)), ) ], - ) + ), + Container( + child: Row( + children: [ + Expanded( + flex: 1, + child: Container(), + ), + Expanded( + flex: 1, + child: Container( + //margin: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + children: [ + buildClipRect(primaryColor,Icons.remove, () {}), + buildClipRect(primaryColor,Icons.add, () {}), + Expanded( + child: Container( + ), + ), + buildClipRect(redColor,Icons.close, () {}), + ], + )), + ) + ], + ), + ), + Divider() ], ), ); } + + ClipRect buildClipRect(Color color, IconData icon, VoidCallback onPress) { + return ClipRect( + child: Container( + margin: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: color + ), + height: 35, + width: 35, + child: IconButton( + icon: Icon(icon, color: whiteColor, size: 20), + onPressed: onPress, + ), + ), + ); + } } diff --git a/lib/widgets/components/calculator/calculator-button.dart b/lib/widgets/components/calculator/calculator-button.dart index 161d128..1a10148 100644 --- a/lib/widgets/components/calculator/calculator-button.dart +++ b/lib/widgets/components/calculator/calculator-button.dart @@ -14,7 +14,7 @@ class CalculatorButton extends StatelessWidget { child: Container( decoration: BoxDecoration( border: Border.all( - color: Color.fromRGBO(0, 0, 0, 0.1), + color: const Color.fromRGBO(0, 0, 0, 0.1), width: 0.5, ), ), @@ -24,7 +24,7 @@ class CalculatorButton extends StatelessWidget { text, style: TextStyle(fontSize: 25, fontWeight: FontWeight.w500), ), - padding: EdgeInsets.all(15), + padding: const EdgeInsets.all(15), highlightColor: Colors.blueGrey[100], splashColor: Colors.blueAccent[100], ))); diff --git a/pubspec.lock b/pubspec.lock index 8b3f5db..244b923 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -83,6 +83,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + flutter_redux: + dependency: "direct main" + description: + name: flutter_redux + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" flutter_test: dependency: "direct dev" description: flutter @@ -173,7 +180,7 @@ packages: source: hosted version: "1.6.4" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" @@ -242,6 +249,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + redux: + dependency: "direct main" + description: + name: redux + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + redux_thunk: + dependency: "direct main" + description: + name: redux_thunk + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" responsive_builder: dependency: "direct main" description: @@ -254,6 +275,13 @@ packages: description: flutter source: sdk version: "0.0.99" + sliding_up_panel: + dependency: "direct main" + description: + name: sliding_up_panel + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" source_span: dependency: transitive description: @@ -261,6 +289,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" stack_trace: dependency: transitive description: @@ -289,6 +331,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8c98e42..13bc3d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,9 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 + redux: ^4.0.0 + flutter_redux: ^0.6.0 + redux_thunk: ^0.3.0 stacked : ^1.5.2 provider_architecture: ^1.0.3 responsive_builder: ^0.1.4 @@ -14,9 +17,12 @@ dependencies: logger: ^0.9.1 get_it: ^3.0.3 equatable: ^1.1.1 - http: ^0.12.1 + http: ^0.12.1 + sqflite: ^1.3.0 + path_provider: ^1.6.9 google_fonts: ^1.1.0 flutter_boom_menu: ^1.0.2 + sliding_up_panel: ^1.0.2 material_design_icons_flutter: ^4.0.5345 dev_dependencies: flutter_test: