From d3d983ba3ffe979ad0943b2d2cd8222b40589b48 Mon Sep 17 00:00:00 2001 From: "Serik.Uvaissov" Date: Thu, 25 Jun 2020 11:39:17 +0600 Subject: [PATCH] pre release --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 6 +- .../aman_kassa_flutter/MainActivity.kt | 73 ++++++++++- android/build.gradle | 2 +- ios/Runner/Info.plist | 2 + lib/core/models/user.dart | 21 +++- lib/core/services/ApiService.dart | 44 ++++++- lib/core/services/DataService.dart | 52 ++++++-- lib/redux/actions/calc_actions.dart | 4 + lib/redux/actions/kassa_actions.dart | 4 + lib/redux/actions/user_actions.dart | 62 +++++++++- lib/redux/constants/auth_type_const.dart | 2 + lib/redux/state/setting_state.dart | 21 +++- lib/redux/state/user_state.dart | 96 +++++++++----- lib/redux/store.dart | 42 +++++-- lib/views/check/image_show_container.dart | 104 +++++++++++++++- lib/views/home/home_view.dart | 14 ++- lib/views/home/tabs/CalculatorTab.dart | 9 +- lib/views/home/tabs/KassaTab.dart | 4 +- lib/views/login/login_view.dart | 79 ++++++++++-- lib/views/payment/payment_view.dart | 21 +++- lib/widgets/fields/busy_button.dart | 27 ++-- pubspec.lock | 117 ++++++++++++------ pubspec.yaml | 9 +- 24 files changed, 680 insertions(+), 137 deletions(-) create mode 100644 lib/redux/constants/auth_type_const.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 44b44b4..849a2c2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -47,7 +47,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.aman_kassa_flutter" - minSdkVersion 16 + minSdkVersion 18 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c25a2fd..10582c7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,9 +6,13 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + + + + + if (call.method == "getBatteryLevel") { + val batteryLevel = getBatteryLevel() + + if (batteryLevel != -1) { + result.success(batteryLevel) + } else { + result.error("UNAVAILABLE", "Battery level not available.", null) + } + } else if (call.method == "sendMessage") { + val batteryLevel = sendMessage() + + if (batteryLevel != -1) { + result.success(batteryLevel) + } else { + result.error("UNAVAILABLE", "Battery level not available.", null) + } + } else { + result.notImplemented() + } + } + } + + private fun getBatteryLevel(): Int { + val batteryLevel: Int + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager + batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) + } else { + val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + } + println("batteryLevel"); + println(batteryLevel); + return batteryLevel + } + + private fun sendMessage(): Int { + val packageManager: PackageManager = context.packageManager + val i = Intent(Intent.ACTION_VIEW) + try { + val mobileNo: String = "77774904900" //call.argument("mobileNo") + val message: String = "Hello world" //call.argument("message") + //https://wa.me/919167370647?text=Yes%20We'll%20do%20this%20in%20frag4%20inOCW + println("mobileNo: $mobileNo message: $message") + val url = "https://wa.me/" + mobileNo.trim { it <= ' ' } + "?text=" + message.trim { it <= ' ' } + i.setPackage("com.whatsapp") + i.data = Uri.parse(url) + if (i.resolveActivity(packageManager) != null) { + context.startActivity(i) + } + println("finish method - 2") + } catch (e: Exception) { + e.printStackTrace() + } + return 25 } } diff --git a/android/build.gradle b/android/build.gradle index 3100ad2..1eddda3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.3.61' repositories { google() jcenter() diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 59e3452..0adb400 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + NSCameraUsageDescription + Camera permission is required for barcode and qr-code scanning. CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/lib/core/models/user.dart b/lib/core/models/user.dart index 6ed4a42..03ea279 100644 --- a/lib/core/models/user.dart +++ b/lib/core/models/user.dart @@ -9,14 +9,27 @@ class User { User({this.email, this.fullName, this.name, this.token, this.appCompanyId, this.kassaId}); - factory User.fromJson(Map json) { - return User ( + static User fromJson(Map json) { + return json != null + ? User ( name: json['name'], email: json['mail'], token: json['api_token'], fullName: json['fullname'], appCompanyId: json['app_company_id'] as int, kassaId: json['kassa_id'] as int, - ); - } + ) + : null; + } + + dynamic toJson() { + return { + "name": name, + "mail": email, + "api_token": token, + "fullname": fullName, + "app_company_id": appCompanyId, + "kassa_id": kassaId + }; + } } diff --git a/lib/core/services/ApiService.dart b/lib/core/services/ApiService.dart index 3ec151c..f7f3a98 100644 --- a/lib/core/services/ApiService.dart +++ b/lib/core/services/ApiService.dart @@ -2,10 +2,14 @@ import 'dart:convert'; import 'dart:io'; import 'package:aman_kassa_flutter/core/base/base_service.dart'; -import 'package:aman_kassa_flutter/core/entity/Goods.dart'; +import 'package:device_info/device_info.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/smena.dart'; +import 'package:aman_kassa_flutter/core/route_names.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/core/locator.dart'; import '../models/auth_response.dart'; import 'package:http/http.dart' as http; @@ -13,6 +17,8 @@ import 'package:http/http.dart' as http; /// The service responsible for networking requests class ApiService extends BaseService { static const endpoint = 'https://kassa-test.aman.com.kz/ru/api/v2'; + final NavigatorService _navigatorService = locator(); + final DialogService _dialogService = locator(); var client = new http.Client(); @@ -27,16 +33,27 @@ class ApiService extends BaseService { return aman.body; } + Future authenticate_token(String token, { bool statusCheck = true}) async { + Map requestBody = { + 'token': token + }; + String response = await requestFormData('/activate_token', requestBody, statusCheck: statusCheck ); + + AuthResponse aman = AuthResponse.fromJson(json.decode(response)); + return aman.body; + } + Future> isActive(String token) async { Map requestBody = {'api_token': token}; var response = await requestFormData('/test_auth', requestBody); return Response.fromJson(json.decode(response), Message.fromJson); } - Future> logout(String token) async { + Future> logout(String token) async { Map requestBody = {'api_token': token}; var response = await requestFormData('/logout', requestBody); - return Response.fromJson(json.decode(response), Message.fromJson); + print(json.decode(response)); + return Response.fromJsonDynamic(json.decode(response)); } Future> money(String token) async { @@ -102,10 +119,28 @@ class ApiService extends BaseService { Future requestFormData(String point, Map requestBody, { bool statusCheck = true } ) async { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + + Map headers = { HttpHeaders.contentTypeHeader: "multipart/form-data", HttpHeaders.cacheControlHeader: "no-cache" }; + if(Platform.isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + print(androidInfo.model); + headers.addAll({ + HttpHeaders.userAgentHeader: androidInfo.model, + }); + } + + if(Platform.isIOS) { + IosDeviceInfo iosInfo = await deviceInfo.iosInfo; + print(iosInfo.utsname.machine); + headers.addAll({ + HttpHeaders.userAgentHeader: iosInfo.utsname.machine, + }); + } var uri = Uri.parse('$endpoint$point'); var request = http.MultipartRequest('POST', uri) @@ -118,7 +153,8 @@ class ApiService extends BaseService { if(statusCheck) { //Проверка на авторизованный запрос, необязательный параметр Response check = Response.fromJsonDynamic(json.decode(body)); if (!check.operation && check.status == 401) { - print('object'); + _dialogService.showDialog(description: 'Необходимо пройти повторную авторизацию'); + _navigatorService.replace(LoginViewRoute); } } return body; diff --git a/lib/core/services/DataService.dart b/lib/core/services/DataService.dart index 2b64e2c..6d173a5 100644 --- a/lib/core/services/DataService.dart +++ b/lib/core/services/DataService.dart @@ -5,6 +5,7 @@ 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/calc_model.dart'; import 'package:aman_kassa_flutter/core/models/check_data.dart'; import 'package:aman_kassa_flutter/core/models/check_item.dart'; import 'package:aman_kassa_flutter/core/models/product_dao.dart'; @@ -12,6 +13,7 @@ import 'package:aman_kassa_flutter/core/models/response.dart'; import 'package:aman_kassa_flutter/core/models/user.dart'; import 'package:aman_kassa_flutter/core/services/DbService.dart'; import 'package:aman_kassa_flutter/redux/constants/operation_const.dart'; +import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:aman_kassa_flutter/redux/store.dart'; import 'ApiService.dart'; @@ -38,9 +40,7 @@ class DataService extends BaseService { } CheckData _transformProductsToCheckData( - {String paymentType, - String tradeType, - List items}) { + {String paymentType, String tradeType, List items}) { List itemsList = []; int iterator = 1; num summ = 0.0; @@ -67,16 +67,52 @@ class DataService extends BaseService { return checkData; } + CheckData _transformCalcModelToCheckData( + {String paymentType, String tradeType, List items}) { + List itemsList = []; + int iterator = 1; + num summ = 0.0; + items.forEach((el) { + int articul = iterator; + CheckItem item = CheckItem( + name: 'Позиция $iterator', + cnt: el.num2 != null ? double.parse(el.num2) : 1.0, + price: double.parse(el.num1) , + articul: articul); + + summ += item.cnt * item.price; + itemsList.add(item); + iterator++; + }); + CheckData checkData = CheckData(type: tradeType, items: itemsList); + if ((paymentType ?? 'cash') == 'card') { + checkData.card = summ; + } + print(checkData); + return checkData; + } + Future> sellOrReturn( {String paymentType, String tradeType, String token, - List items, - String operationType}) async { + List kassaItems, + List calcItems, + String operationType, + String mode}) async { try { - CheckData checkData = _transformProductsToCheckData( - paymentType: paymentType, tradeType: tradeType, items: items); - String data = jsonEncode(checkData.toJson()); + String data; + if(mode == SettingModeKassa) { + CheckData checkData = _transformProductsToCheckData( + paymentType: paymentType, tradeType: tradeType, items: kassaItems); + data = jsonEncode(checkData.toJson()); + } else if(mode == SettingModeCalc) { + CheckData checkData = _transformCalcModelToCheckData( + paymentType: paymentType, tradeType: tradeType, items: calcItems); + data = jsonEncode(checkData.toJson()); + } + + log.i('token: $token'); log.i('data: $data'); Response response = await (operationType == OperationTypePay diff --git a/lib/redux/actions/calc_actions.dart b/lib/redux/actions/calc_actions.dart index 5e8c351..05e745a 100644 --- a/lib/redux/actions/calc_actions.dart +++ b/lib/redux/actions/calc_actions.dart @@ -32,6 +32,10 @@ Future setEqual(Store store) async { store.dispatch(SetCalcStateAction(CalcState(isEqual: true))); } +Future cleanCalcItems(Store store) async { + store.dispatch(SetCalcStateAction(CalcState(calcItems: []))); +} + ThunkAction onTapAction(String value) { return (Store store) async { diff --git a/lib/redux/actions/kassa_actions.dart b/lib/redux/actions/kassa_actions.dart index 7f4e621..61ad660 100644 --- a/lib/redux/actions/kassa_actions.dart +++ b/lib/redux/actions/kassa_actions.dart @@ -38,6 +38,10 @@ Future backBottomElement(Store store) async { } } +Future cleanKassaItems(Store store) async { + store.dispatch(SetKassaStateAction(KassaState(kassaItems: []))); +} + ThunkAction addCustomProductToKassaItems(String name, int count, double price, double total) { return (Store store) async { List items = store.state.kassaState.kassaItems; diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index ef9ecdc..af8fb01 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -10,6 +10,7 @@ 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/constants/auth_type_const.dart'; import 'package:aman_kassa_flutter/redux/state/user_state.dart'; import 'package:redux/redux.dart'; import 'package:meta/meta.dart'; @@ -28,8 +29,14 @@ 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; + String token = store.state.userState.user?.token; + bool isAuthenticated = false; + if(token!=null) { + Response session = await _api.isActive(token); + isAuthenticated = "OK" == session.body.message; + } else { + await Future.delayed(Duration(milliseconds: 100)); + } store.dispatch( SetUserStateAction( UserState( @@ -41,14 +48,59 @@ Future checkUserAction(Store store) async { if(!isAuthenticated){ _navigation.replace(LoginViewRoute); + } else { + _navigation.replace(HomeViewRoute); } + } catch (error) { + print(error); + store.dispatch(SetUserStateAction(UserState(isLoading: false))); + } +} + + +Future logoutAction(Store store) async { + try { + store.dispatch( + SetUserStateAction( + UserState( + isLoading: false, + isAuthenticated: false, + user: User(), + ), + ), + ); + _navigation.replace(LoginViewRoute); } catch (error) { store.dispatch(SetUserStateAction(UserState(isLoading: false))); } } +ThunkAction authenticateToken(String token) { + return (Store store) async { + store.dispatch(SetUserStateAction(UserState(isLoading: true))); + try { + AuthBody result = await _api.authenticate_token(token, statusCheck: false); + store.dispatch(SetUserStateAction(UserState( + isLoading: false, + loginFormMessage: LoginFormMessage(email: result.email?.join(","), password: result.password?.join(","), message: result.message), + user: result.user, + authenticateType: AuthenticateTypeQr, + isAuthenticated: result.user != null, + ))); + 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) { + print(e); + store.dispatch(SetUserStateAction(UserState(isLoading: false))); + } + }; +} ThunkAction authenticate(String email, String password) { return (Store store) async { @@ -58,7 +110,11 @@ ThunkAction authenticate(String email, String password) { store.dispatch(SetUserStateAction(UserState( isLoading: false, loginFormMessage: LoginFormMessage(email: result.email?.join(","), password: result.password?.join(","), message: result.message), - user: result.user + user: result.user, + login: email, + password: password, + authenticateType: AuthenticateTypeLogin, + isAuthenticated: result.user != null, ))); if(result.user == null && result.message!=null){ _dialogService.showDialog(title: 'Warning', buttonTitle: 'Ok', description: result.message); diff --git a/lib/redux/constants/auth_type_const.dart b/lib/redux/constants/auth_type_const.dart new file mode 100644 index 0000000..2227403 --- /dev/null +++ b/lib/redux/constants/auth_type_const.dart @@ -0,0 +1,2 @@ +const String AuthenticateTypeQr = 'AuthenticateTypeQr'; +const String AuthenticateTypeLogin = 'AuthenticateTypeLogin'; \ No newline at end of file diff --git a/lib/redux/state/setting_state.dart b/lib/redux/state/setting_state.dart index 3443d09..33797bd 100644 --- a/lib/redux/state/setting_state.dart +++ b/lib/redux/state/setting_state.dart @@ -8,8 +8,14 @@ class SettingState { SettingState({this.mode, this.tradeType}); - factory SettingState.initial() => SettingState(mode: SettingModeCalc, tradeType: SettingTradeTypeGood); + //read hive + factory SettingState.initial(SettingState payload) { + return SettingState( + mode: payload?.mode ?? SettingModeCalc, + tradeType: payload?.tradeType ?? SettingTradeTypeGood); + } + //write hive SettingState copyWith({ @required mode, @required tradeType, @@ -19,4 +25,17 @@ class SettingState { tradeType: tradeType ?? this.tradeType, ); } + + static SettingState fromJson(dynamic json) { + return json != null + ? SettingState( + tradeType: json['tradeType'], + mode: json['mode'], + ) + : null; + } + + dynamic toJson() { + return {"tradeType": tradeType, "mode": mode}; + } } diff --git a/lib/redux/state/user_state.dart b/lib/redux/state/user_state.dart index eb32786..19401eb 100644 --- a/lib/redux/state/user_state.dart +++ b/lib/redux/state/user_state.dart @@ -7,49 +7,85 @@ class UserState { final bool isError; final bool isLoading; final bool isAuthenticated; + final String authenticateType; + final String login; + final String password; final LoginFormMessage loginFormMessage; final User user; final Smena smena; - UserState({ - this.isError, - this.isLoading, - this.isAuthenticated, - this.user, - this.loginFormMessage, - this.smena - }); + UserState( + {this.isError, + this.isLoading, + this.isAuthenticated, + this.authenticateType, + this.login, + this.password, + this.user, + this.loginFormMessage, + this.smena}); - factory UserState.initial() => UserState( - isLoading: false, - isError: false, - isAuthenticated: false, - loginFormMessage: LoginFormMessage(), - smena: Smena(), - ); + factory UserState.initial(UserState payload) => UserState( + isLoading: false, + isError: false, + isAuthenticated: false, + loginFormMessage: LoginFormMessage(), + smena: Smena(), + user: payload?.user ?? User(), + authenticateType: payload?.authenticateType ?? null, + login: payload?.login ?? null, + password: payload?.password ?? null, + ); - UserState copyWith({ - @required bool isError, - @required bool isLoading, - @required User user, - @required bool isAuthenticated, - @required LoginFormMessage loginFormMessage, - @required Smena smena, - }) { + UserState copyWith( + {@required bool isError, + @required bool isLoading, + @required User user, + @required bool isAuthenticated, + @required LoginFormMessage loginFormMessage, + @required Smena smena, + @required String authenticateType, + @required String login, + @required String password, + }) { return UserState( - isError: isError ?? this.isError, - isLoading: isLoading ?? this.isLoading, - isAuthenticated: isAuthenticated ?? this.isAuthenticated, - user: user ?? this.user, - loginFormMessage: loginFormMessage ?? this.loginFormMessage, - smena: smena ?? this.smena, + isError: isError ?? this.isError, + isLoading: isLoading ?? this.isLoading, + isAuthenticated: isAuthenticated ?? this.isAuthenticated, + user: user ?? this.user, + loginFormMessage: loginFormMessage ?? this.loginFormMessage, + smena: smena ?? this.smena, + authenticateType: authenticateType ?? this.authenticateType, + login: login ?? this.login, + password: password ?? this.password, ); } + + static UserState fromJson(dynamic json) { + return json != null + ? UserState( + user: User.fromJson(json['user']), + authenticateType: json['authenticateType'], + login: json['login'], + password: json['password'] + ) + : null; + } + + dynamic toJson() { + return { + "user": user != null ? user.toJson() : null, + "authenticateType": authenticateType, + "login": login, + "password": password, + }; + } } 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 index 2b6b906..26c5e8d 100644 --- a/lib/redux/store.dart +++ b/lib/redux/store.dart @@ -11,7 +11,10 @@ import 'package:aman_kassa_flutter/redux/state/setting_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_persist_flutter/redux_persist_flutter.dart'; import 'package:redux_thunk/redux_thunk.dart'; +import 'package:redux_persist/redux_persist.dart'; +import 'dart:io'; import 'actions/calc_actions.dart'; @@ -46,10 +49,10 @@ class AppState { final CalcState calcState; AppState({ - @required this.userState, - @required this.kassaState, - @required this.settingState, - @required this.calcState, + this.userState, + this.kassaState, + this.settingState, + this.calcState, }); //stable work @@ -66,6 +69,23 @@ class AppState { calcState: calcState ?? this.calcState, ); } + + static AppState fromJson(dynamic json){ + print(json); + return json !=null + ? AppState( + settingState: SettingState.fromJson(json['settingState']), + userState: UserState.fromJson(json['userState']), + ) + : null; + } + + dynamic toJson() { + return { + "settingState": settingState.toJson(), + "userState" : userState.toJson(), + }; + } } class Redux { @@ -81,14 +101,22 @@ class Redux { //initial context static Future init() async { - final userStateInitial = UserState.initial(); + // Create Persistor + final persist = Persistor( + storage: FlutterStorage(), // Or use other engines + serializer: JsonSerializer(AppState.fromJson), // Or use other serializers + ); + + final initialState = await persist.load(); + + final userStateInitial = UserState.initial(initialState?.userState); final kassaStateInitial = KassaState.initial(); - final settingStateInitial = SettingState.initial(); + final settingStateInitial = SettingState.initial(initialState?.settingState); final calcStateInitial = CalcState.initial(); _store = Store( appReducer, - middleware: [thunkMiddleware], + middleware: [thunkMiddleware, persist.createMiddleware() ], initialState: AppState( userState: userStateInitial, kassaState: kassaStateInitial, diff --git a/lib/views/check/image_show_container.dart b/lib/views/check/image_show_container.dart index c51a1eb..ac7dfc4 100644 --- a/lib/views/check/image_show_container.dart +++ b/lib/views/check/image_show_container.dart @@ -1,10 +1,16 @@ import 'dart:convert'; 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:flutter/material.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +//import 'package:flutter/services.dart'; class ImageShowContainer extends StatelessWidget { final ImageShowModel data; + ImageShowContainer(this.data); + @override Widget build(BuildContext context) { return Scaffold( @@ -13,12 +19,12 @@ class ImageShowContainer extends StatelessWidget { title: Text(data.title), ), body: ListView( - children: [ - imageFromBase64String(data.data) - ], + children: [imageFromBase64String(data.data)], ), + floatingActionButton: MyFloatingActionButton(), ); } + } Padding imageFromBase64String(String base64String) { @@ -28,9 +34,99 @@ Padding imageFromBase64String(String base64String) { ); } - class ImageShowModel { final String data; final String title; + ImageShowModel(this.data, this.title); +} + +class MyFloatingActionButton extends StatefulWidget { + @override + _MyFloatingActionButtonState createState() => _MyFloatingActionButtonState(); +} + +class _MyFloatingActionButtonState extends State { + bool showFab = true; +// String _batteryLevel = 'Unknown battery level.'; +// static const platform = const MethodChannel('samples.flutter.dev/battery'); +// +// Future _getBatteryLevel() async { +// String batteryLevel; +// try { +// final int result = await platform.invokeMethod('sendMessage'); +// print(result); +// batteryLevel = 'Battery level at $result % .'; +// } on PlatformException catch (e) { +// batteryLevel = "Failed to get battery level: '${e.message}'."; +// } +// +// setState(() { +// _batteryLevel = batteryLevel; +// }); +// } + + @override + Widget build(BuildContext context) { + return showFab + ? FloatingActionButton( + child: Icon(Icons.share), + onPressed: () { + var bottomSheetController = showBottomSheet( + context: context, + builder: (context) => Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0 ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(15)), + boxShadow: [ + BoxShadow( + blurRadius: 10, color: Colors.grey[300], spreadRadius: 5) + ]), + //color: Colors.grey[900], + height: 280, + child: Column( + children: [ + verticalSpaceSmall, + BusyButton(title: 'Электронная почта', onPressed: () {} , mainColor: primaryColor, icon: Icons.mail, ), + verticalSpaceSmall, + BusyButton(title: 'WhatsApp', onPressed: () {} , mainColor: greenColor, icon: MdiIcons.whatsapp, ), + verticalSpaceSmall, + BusyButton(title: '', onPressed: () {} , mainColor: yellowColor, icon: Icons.share,), + ], + ) + )); + showFoatingActionButton(false); + bottomSheetController.closed.then((value) { + showFoatingActionButton(true); + }); + }, + ): Container() ; + } + void showFoatingActionButton(bool value) { + setState(() { + showFab = value; + }); + } +} + + +class DecoratedTextField extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + decoration: BoxDecoration( + //color: fillColor, borderRadius: BorderRadius.circular(10) + ), + child: TextField( + decoration: InputDecoration( + prefixIcon: Icon(Icons.mail_outline, color: primaryColor,), + labelStyle: TextStyle(color: primaryColor), + labelText: 'Отправить на Email', + ), + )); + } } \ No newline at end of file diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart index ab2804f..e8411ad 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -1,7 +1,12 @@ import 'package:aman_kassa_flutter/core/locator.dart'; import 'package:aman_kassa_flutter/core/logger.dart'; import 'package:aman_kassa_flutter/core/models/choice.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/route_names.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/actions/user_actions.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:aman_kassa_flutter/redux/state/setting_state.dart'; @@ -40,6 +45,8 @@ class _HomeViewState extends State { PageController pageController; int selectedTabIndex; DataService _dataService = locator(); + ApiService _api = locator(); + NavigatorService _navigatorService = locator(); final GlobalKey _keyLoader = new GlobalKey(); @override @@ -58,7 +65,12 @@ class _HomeViewState extends State { void _onSelectChoice(Choice choice) async { if (choice.command == 'exit') { - + Dialogs.showLoadingDialog(context, _keyLoader); + Response result = await _api.logout(Redux.store.state.userState.user.token); + if(result.operation && result.status == 200) { + Redux.store.dispatch(logoutAction); + } + Navigator.of(_keyLoader.currentContext, rootNavigator: true).pop(); } else if (choice.command == 'update') { Dialogs.showLoadingDialog(context, _keyLoader); bool result = await _dataService.getDataFromServer(Redux.store.state.userState.user); diff --git a/lib/views/home/tabs/CalculatorTab.dart b/lib/views/home/tabs/CalculatorTab.dart index 4e071ba..0be063f 100644 --- a/lib/views/home/tabs/CalculatorTab.dart +++ b/lib/views/home/tabs/CalculatorTab.dart @@ -40,12 +40,11 @@ class CalculatorTab extends StatelessWidget { Row( children: [ Expanded( - child: RaisedButton( - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(8.0), color: redColor, child: Text( - "Возврат", + "возврат", style: buttonBigTitleTextStyle, ), onPressed: () { @@ -55,11 +54,11 @@ class CalculatorTab extends StatelessWidget { ), Expanded( child: RaisedButton( - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(8.0), color: greenColor, child: Text( - "Оплата", + "оплата", style: buttonBigTitleTextStyle, ), onPressed: () { diff --git a/lib/views/home/tabs/KassaTab.dart b/lib/views/home/tabs/KassaTab.dart index 8f31f8f..62cd58a 100644 --- a/lib/views/home/tabs/KassaTab.dart +++ b/lib/views/home/tabs/KassaTab.dart @@ -107,7 +107,7 @@ class KassaTab extends StatelessWidget { padding: EdgeInsets.all(8), color: redColor, child: Text( - "Возврат", + "возврат", style: buttonBigTitleTextStyle, ), onPressed: () { @@ -123,7 +123,7 @@ class KassaTab extends StatelessWidget { padding: EdgeInsets.all(8), color: greenColor, child: Text( - "Оплата", + "оплата", style: buttonBigTitleTextStyle, ), onPressed: () { diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart index 3bf0345..8c14fe8 100644 --- a/lib/views/login/login_view.dart +++ b/lib/views/login/login_view.dart @@ -1,3 +1,7 @@ +import 'dart:ui'; + +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/services/dialog_service.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'; @@ -5,9 +9,14 @@ 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:barcode_scan/gen/protos/protos.pb.dart'; +import 'package:barcode_scan/gen/protos/protos.pbenum.dart'; +import 'package:barcode_scan/model/scan_options.dart'; +import 'package:barcode_scan/platform_wrapper.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter/material.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class LoginView extends StatelessWidget { final emailController = TextEditingController(text: 'test@kkm-kassa.kz'); @@ -15,10 +24,7 @@ class LoginView extends StatelessWidget { final FocusNode passwordNode = new FocusNode(); final GlobalKey _scaffoldKey = new GlobalKey(); - - _pressBtnEnter() async { - Redux.store.dispatch(authenticate(emailController.text, passwordController.text)); - } + final DialogService _dialogService = locator(); @override Widget build(BuildContext context) { @@ -35,11 +41,17 @@ class LoginView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: 150, - child: Image.asset('assets/images/logo.png'), - //child: FlutterLogo(size: 120,), + Stack( + alignment: Alignment.bottomLeft, + children: [ + SizedBox( + height: 150, + child: Image.asset('assets/images/logo.png'), + ), + Positioned(child: Text('онлайн касса', style: TextStyle(fontWeight: FontWeight.bold),), bottom: 23.0,left: 25.0,), + ], ), + InputField( placeholder: 'Электронная почта', controller: emailController, @@ -68,9 +80,15 @@ class LoginView extends StatelessWidget { ], ), verticalSpaceLarge, - TextLink( - 'Регистрация', - onPressed: () {}, +// TextLink( +// 'Регистрация', +// onPressed: () {}, +// ), + IconButton( + icon: Icon(MdiIcons.qrcodeScan), + iconSize: 40, + tooltip: "Scan", + onPressed: scan, ) ], ), @@ -78,5 +96,42 @@ class LoginView extends StatelessWidget { }); } + _pressBtnEnter() async { + Redux.store + .dispatch(authenticate(emailController.text, passwordController.text)); + } + Future scan() async { + try { + var options = ScanOptions(strings: { + "cancel": 'Отмена', + "flash_on": 'Вкл фонарик', + "flash_off": 'Выкл фонарик', + }); + var result = await BarcodeScanner.scan(options: options); + print(result.type); // The result type (barcode, cancelled, failed) + print(result.rawContent); // The barcode content + print(result.format); // The barcode format (as enum) + print(result + .formatNote); // If a unknown format was scanned this field contains a note + if (result.type == ResultType.Barcode && + result.rawContent?.length == 60) { + Redux.store.dispatch(authenticateToken(result.rawContent)); + } else { + _dialogService.showDialog(description: 'Не верный формат QR кода'); + } + } on PlatformException catch (e) { + var result = ScanResult.create(); + result.type = ResultType.Error; + result.format = BarcodeFormat.unknown; + if (e.code == BarcodeScanner.cameraAccessDenied) { + result.rawContent = 'The user did not grant the camera permission!'; + _dialogService.showDialog( + description: 'Нет доступа до камеры устройства'); + } else { + result.rawContent = 'Unknown error: $e'; + _dialogService.showDialog(description: 'Неизвестная ошибка: $e'); + } + } + } } diff --git a/lib/views/payment/payment_view.dart b/lib/views/payment/payment_view.dart index b5dd9fb..137ad3b 100644 --- a/lib/views/payment/payment_view.dart +++ b/lib/views/payment/payment_view.dart @@ -6,6 +6,8 @@ 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/dialog_service.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/redux/actions/calc_actions.dart'; +import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart'; import 'package:aman_kassa_flutter/redux/constants/operation_const.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:aman_kassa_flutter/redux/state/calc_state.dart'; @@ -82,7 +84,7 @@ class _PaymentViewState extends State { if(widget.model.mode == SettingModeCalc) { return StoreConnector( converter: (store) => store.state.calcState, - builder: (context, vm) { + builder: (_, vm) { return Text('${totalCalc(vm.calcItems)} тнг', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.black87, @@ -92,7 +94,7 @@ class _PaymentViewState extends State { } return StoreConnector( converter: (store) => store.state.kassaState, - builder: (context, vm) { + builder: (_, vm) { return Text('${totalKassa(vm.kassaItems)} тнг', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black87, fontSize: 35 )); } ); @@ -128,13 +130,24 @@ class _PaymentViewState extends State { AppState _state = Redux.store.state; String _token = _state.userState.user.token; String _tradeType = _state.settingState.tradeType; - List items = _state.kassaState.kassaItems; - Response response = await _dataService.sellOrReturn(token: _token, items: items, paymentType: type, operationType: widget.model.operationType, tradeType: _tradeType ); + String _mode = _state.settingState.mode; + if(_mode == SettingModeCalc){ + _tradeType = SettingTradeTypeGood; + } + List kassaItems = _state.kassaState.kassaItems; + List calcItems = _state.calcState.calcItems; + Response response = await _dataService.sellOrReturn(token: _token, kassaItems: kassaItems, paymentType: type, operationType: widget.model.operationType, tradeType: _tradeType, calcItems: calcItems, mode: _mode ); Navigator.of(context, rootNavigator: true).pop(); setState(() { isBusy = false; }); if(response.operation){ String message = response.body['message']; String check = response.body['check']; + +// if(_mode == SettingModeCalc){ +// Redux.store.dispatch(cleanCalcItems); +// } else if(_mode == SettingModeKassa) { +// Redux.store.dispatch(cleanKassaItems); +// } _navigatorService.pop(); _navigatorService.push(ImageShowRoute, arguments: ImageShowModel(check, message)); } else if(!response.operation && response.status !=500) { diff --git a/lib/widgets/fields/busy_button.dart b/lib/widgets/fields/busy_button.dart index c891e18..d5328d8 100644 --- a/lib/widgets/fields/busy_button.dart +++ b/lib/widgets/fields/busy_button.dart @@ -1,5 +1,6 @@ import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/shared_styles.dart'; +import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; import 'package:flutter/material.dart'; /// A button that shows a busy indicator in place of title @@ -9,13 +10,15 @@ class BusyButton extends StatefulWidget { final Function onPressed; final bool enabled; final Color mainColor; + final IconData icon; const BusyButton( { @required this.title, this.busy = false, @required this.onPressed, this.enabled = true, - this.mainColor + this.mainColor, + this.icon }); @override @@ -46,14 +49,20 @@ class _BusyButtonState extends State { margin: EdgeInsets.symmetric( horizontal: widget.busy ? 10 : 25, vertical: widget.busy ? 10 : 15), - child: !widget.busy - ? Text( - widget.title, - style: buttonTitleTextStyle, - ) - : CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white)), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.icon!=null ? Container(child: (Icon(widget.icon, color: whiteColor,)), margin: const EdgeInsets.only(right: 10.0 ),) : (Container()), + !widget.busy + ? Text( + widget.title, + style: buttonTitleTextStyle, + ) + : CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white)), + ], + ), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 883b5ca..a4b9a29 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.4.1" + barcode_scan: + dependency: "direct main" + description: + name: barcode_scan + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" boolean_selector: dependency: transitive description: @@ -64,6 +71,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + device_info: + dependency: "direct main" + description: + name: device_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2+4" equatable: dependency: "direct main" description: @@ -71,18 +85,25 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + esys_flutter_share: + dependency: "direct main" + description: + name: esys_flutter_share + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_boom_menu: - dependency: "direct main" - description: - name: flutter_boom_menu - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" flutter_redux: dependency: "direct main" description: @@ -95,6 +116,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" get_it: dependency: "direct main" description: @@ -172,13 +198,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.4" - observable_ish: - dependency: transitive - description: - name: observable_ish - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" path: dependency: transitive description: @@ -235,6 +254,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + protobuf: + dependency: transitive + description: + name: protobuf + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" provider: dependency: "direct main" description: @@ -242,13 +268,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.2" - provider_architecture: - dependency: "direct main" - description: - name: provider_architecture - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1+1" quiver: dependency: transitive description: @@ -263,6 +282,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + redux_persist: + dependency: "direct main" + description: + name: redux_persist + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4" + redux_persist_flutter: + dependency: "direct main" + description: + name: redux_persist_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.2" redux_thunk: dependency: "direct main" description: @@ -277,18 +310,39 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.9" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.7+3" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+10" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2+7" sky_engine: dependency: transitive 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: @@ -317,13 +371,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.3" - stacked: - dependency: "direct main" - description: - name: stacked - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 889e10f..92d2438 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,8 +10,8 @@ dependencies: redux: ^4.0.0 flutter_redux: ^0.6.0 redux_thunk: ^0.3.0 - stacked : ^1.5.2 - provider_architecture: ^1.0.3 + redux_persist: ^0.8.4 + redux_persist_flutter: ^0.8.2 responsive_builder: ^0.1.4 provider: ^4.1.2 logger: ^0.9.1 @@ -21,10 +21,11 @@ dependencies: 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 intl: ^0.16.1 + barcode_scan: ^3.0.1 + device_info: ^0.4.2+4 + esys_flutter_share: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter