На экране проведения покупки правильно обрабатывается сумма покупки, передается на экран подтверждения

This commit is contained in:
Ivan Murashov
2017-07-24 18:07:30 +03:00
parent 355c05cf06
commit 080c7ec471
9 changed files with 200 additions and 140 deletions

View File

@@ -20,6 +20,7 @@
<application android:name="io.flutter.app.FlutterApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher_app">
<activity android:name="com.dinect.checker.activity.MainActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
@@ -31,6 +32,7 @@
</activity>
<activity android:name="com.dinect.checker.activity.CameraActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"/>
<service

View File

@@ -66,6 +66,10 @@ public class MainActivity extends FlutterActivity {
Intent cameraIntent = new Intent(MainActivity.this, CameraActivity.class);
startActivityForResult(cameraIntent, START_SCANNER_REQUEST_CODE);
break;
case "removeKeys":
mPreferences.edit().remove(PREF_POS_TOKEN).apply();
mPreferences.edit().remove(PREF_POS_MERCHANT_ID).apply();
break;
default:
result.notImplemented();
break;
@@ -80,11 +84,11 @@ public class MainActivity extends FlutterActivity {
finish();
} else if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_OK) {
if (data == null) {
logout();
mChannel.invokeMethod("foo", null);
} else {
String code = data.getExtras().getString("code", null);
if (code == null) {
logout();
mChannel.invokeMethod("foo", null);
} else {
mChannel.invokeMethod("purchase", code);
}
@@ -92,10 +96,8 @@ public class MainActivity extends FlutterActivity {
}
}
private void logout() {
mChannel.invokeMethod("foo", null);
mPreferences.edit().remove(PREF_POS_TOKEN).apply();
mPreferences.edit().remove(PREF_POS_MERCHANT_ID).apply();
private void removeKeys() {
}
public void startScanner() {
@@ -122,4 +124,4 @@ public class MainActivity extends FlutterActivity {
}
}
}

View File

@@ -11,41 +11,47 @@ class FinishRegistrationScreen extends StatefulWidget {
class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
bool _tokenActive = true;
String _merchantID = '';
_RegistrationScreenState() {
if (textFieldValue == "") {
_getSavedMerchantID();
}
}
@override String getTitle() {
return "Регистрация";
}
@override getHint() {
return 'ID merchant';
return 'ID магазина';
}
@overide getMenuButtons(BuildContext context) {
return <Widget>[new IconButton(icon: new Icon(Icons.help_outline), onPressed: () {})];
}
@override Widget _getScreenContent(BuildContext context) {
if (textFieldValue == "") {
_getSavedValue();
}
@override Widget getScreenContent() {
return new Column(children: <Widget>[
getLogo(),
getHintLabel(),
getDecoratedTextWidget(),
_getMessage(),
_getButton(context)
buildButton(new EdgeInsets.only(top: 36.0, left: buttonVerticalMargin, right: buttonVerticalMargin),
buildRaisedButton(context, _tokenActive ? 'ЗАВЕРШИТЬ РЕГИСТРАЦИЮ' : 'ОБНОВИТЬ СТАТУС АКТИВАЦИИ',() => startScanner(context)))
]);
}
_getMerchantID() {
return new Text(_merchantID != null ? _merchantID : '', style: new TextStyle(color: Colors.black, fontSize: 16.0));
@override Widget getTextWidget() {
return new Row(children: <Widget>[new Text(_merchantID != null ? _merchantID : '', style: new TextStyle(color: Colors.black, fontSize: 16.0))]);
}
_getSavedMerchantID() {
const platform = const MethodChannel('com.dinect.checker/instance_id');
platform.invokeMethod('getMerchantID').then((result) {
setState(() {
_merchantID = result;
print(_merchantID);
print('merchanID: ${_merchantID}');
});
});
}
@@ -53,7 +59,7 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
_getMessage() {
return new Container(height: _tokenActive ? 72.0 : 108.0, decoration: _getDecoraionForMessageField(),
margin: new EdgeInsets.only(top: 20.0, left: 12.0, right: 12.0),
padding: new EdgeInsets.only(bottom: 16.0, left: 14.0, right: 14.0),
padding: new EdgeInsets.only(bottom: 22.0, left: 14.0, right: 14.0),
child: new Center(child: new Text(_getMessageText(),
textAlign: TextAlign.center, style: new TextStyle(height: 1.5, fontWeight: FontWeight.bold, fontSize: 14.0, color: _tokenActive ? tokenActiveTextColor : tokenActivateTextColor))));
}
@@ -72,28 +78,32 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
double buttonHeight = 42.0;
double topMargin = 8.0;
return new Container(margin: new EdgeInsets.only(top: topMargin), height: buttonHeight,
child: new RaisedButton(child: new Text(_tokenActive ? 'ЗАВЕРШИТЬ РЕГИСТРАЦИЮ' : 'ОБНОВИТЬ СТАТУС АКТИВАЦИИ',
child: new RaisedButton(child: new Text(,
style: new TextStyle(fontSize: 14.0, color: Colors.white)),
onPressed: () {
if (_tokenActive) {
startScanner(context);
} else {
checkToken(context).then((response) {
print(response.body);
Map parsedMap = JSON.decode(response.body);
// Обновить экран, заменить сообщение о необходимости активации токена, на сообщние о том, что токен активен.
setState(() {
_tokenActive = parsedMap['active'];
});
// if (_tokenActive) {
// startScanner(context);
// } else {
// checkToken(context).then((response) {
}).catchError((error) {
print(error.toString());
return false;
});
}
// print(response.body);
// Map parsedMap = JSON.decode(response.body);
// // Обновить экран, заменить сообщение о необходимости активации токена, на сообщние о том, что токен активен.
// setState(() {
// _tokenActive = parsedMap['active'];
// });
// }).catchError((error) {
// print(error.toString());
// return false;
// });
// }
},
color: primaryColor));
}
}
}

View File

@@ -11,6 +11,8 @@ abstract class BaseState<T> extends State<T> {
String error = null;
String textFieldValue = "";
TextEditingController controller = new TextEditingController();
@override Widget build(BuildContext context) {
return new Scaffold(appBar: getAppBar(context), body: getBody(context));
}
@@ -57,6 +59,13 @@ abstract class BaseState<T> extends State<T> {
}
}
/// Смена состояния экрана при изменении текста в поле ввода.
handleUserInput(String text) {
setState(() {
textFieldValue = text;
});
}
/// Метод возвращает контейнер с установленными отступами, в котором размещен TextField обернутый в BoxDecoration.
getDecoratedTextWidget() {
return new Container(margin: new EdgeInsets.only(left: verticalMargin, right: verticalMargin),
@@ -76,16 +85,21 @@ abstract class BaseState<T> extends State<T> {
/// Метод возвращает BoxDecoration для _getDecoratedInputField
getDecoraionForTextWidget() {
return new BoxDecoration(color: textFieldBackground,
return new BoxDecoration(color: getTextFilledBackground(),
border: new Border.all(color: textBorderColor, width: 1.0,),
borderRadius: new BorderRadius.all(new Radius.circular(4.0)));
}
Color getTextFilledBackground() {
return const Color(0xffefefef);
}
Widget getTextWidget() {
return new TextField(keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed(hintText: getHint(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
onChanged: (text) => _handleUserInput(text));
controller: controller,
onChanged: (text) => handleUserInput(text));
}
/// Индикация ...
@@ -93,5 +107,35 @@ abstract class BaseState<T> extends State<T> {
return new Center(child: loading ? new CircularProgressIndicator() : null);
}
Widget getValueWithTitle(String title, String value) {
return new Container(padding: new EdgeInsets.only(left: verticalMargin, right: verticalMargin, top: 18.0),
child: new Column(children: <Widget>[
new Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[new Text(title, textAlign: TextAlign.left, style: new TextStyle(color: greyTextColor, fontSize: 14.0))]),
new Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[new Text(value, textAlign: TextAlign.left, style: new TextStyle(color: Colors.black, fontSize: 20.0))])
]));
}
Widget buildButton(EdgeInsets margin, Widget widget) {
return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)]));
}
Widget buildRaisedButton(BuildContext context, String text, VoidCallback onPressed) {
return new RaisedButton(child: new Text(text,
style: new TextStyle(color: Colors.white)),
onPressed: onPressed,
color: primaryColor);
}
Widget buildFlatButton(BuildContext context, String title, Color textColor) {
return new Container(height: buttonHeight, child: new FlatButton(child: new Text(title,
style: new TextStyle(color: textColor)),
onPressed: () => startScanner(context)),
decoration: _getDecoraionForScanButton());
}
_getDecoraionForScanButton() {
return new BoxDecoration(
border: new Border.all(color: primaryColor, width: 1.0),
borderRadius: new BorderRadius.all(new Radius.circular(4.0)));
}
}

View File

@@ -33,8 +33,13 @@ const Color tokenActiveTextColor = const Color(0xff1f5a1f);
const Color tokenActivateTextColor = const Color(0xff4e3a19);
const Color greenBackground = const Color(0xff8ae28a);
// Margins
// Dimens
const double verticalMargin = 28.0;
const double buttonVerticalMargin = 64.0;
const double buttonHeight = 48.0;
const double iconHeight = 20.0;
const platform = const MethodChannel('com.dinect.checker/instance_id');
// HttpClient
final httpClient = createHttpClient();
@@ -56,14 +61,12 @@ checkToken(BuildContext context) async {
/// Может производиться с нескольких экранов (splash, finish_registration).
startScanner(BuildContext context) async {
const platform = const MethodChannel('com.dinect.checker/instance_id');
// Канал слушает ловит вызовы методов из "нативной" части приложения.
// Могут быть вызваны либо logaut либо faq, либо purchase.
platform.setMethodCallHandler((MethodCall call) async {
if (call.method == 'foo') {
logout();
logout(context);
} else {
pushRoute(context, new PurchaseScreen());
}
@@ -71,22 +74,24 @@ startScanner(BuildContext context) async {
return result;
});
Navigator.of(context).pop();
await platform.invokeMethod('startScanner');
}
logout(BuildContext context) {
// String url = intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken;
String url = intUrl + 'tokens/' + 'khooi' + '?_dmapptoken=' + intToken;
print(url);
httpClient.delete(url).then((response) {
print(response.body);
const platform = const MethodChannel('com.dinect.checker/instance_id');
platform.invokeMethod('removeKeys');
pushRoute(context, new RegistrationScreen());
}).catchError((error) {
print(error.toString());
});
// httpClient.delete(url).then((response) {
// print(response.body);
// }).catchError((error) {
// print(error.toString());
// });
pushRoute(context, new RegistrationScreen());
}
/// Навигация по приложению.

View File

@@ -14,7 +14,7 @@ class PurchaseScreen extends StatefulWidget {
class PurchaseScreenState<T> extends BaseState<T> {
String _sum = "1234.00";
String integerPart = '', fractionalPart = '';
@override String getTitle() {
return "Проведение покупки";
@@ -27,7 +27,7 @@ class PurchaseScreenState<T> extends BaseState<T> {
@overide getMenuButtons(BuildContext context) {
return <Widget>[
new IconButton(icon: new Icon(Icons.help_outline), onPressed: () {}),
new IconButton(icon: new Image.asset(logout_png, height: 20.0, width: 20.0), onPressed: () => logout(context))
new IconButton(icon: new Image.asset(logout_png, height: iconHeight, width: iconHeight), onPressed: () => logout(context))
];
}
@@ -40,47 +40,53 @@ class PurchaseScreenState<T> extends BaseState<T> {
getValueWithTitle('Вознаграждение', '100%'),
getHintLabel(),
getDecoratedTextWidget(),
buildButton(new EdgeInsets.only(top: 36.0, left: 70.0, right: 70.0), buildRaisedButton(context, 'ЗАВЕРШИТЬ ПОКУПКУ', () => _purchase(context))),
buildButton(new EdgeInsets.only(top: 24.0, left: 70.0, right: 70.0), buildFlatButton(context, 'СКАНИРОВАТЬ', primaryColor))])
buildButton(new EdgeInsets.only(top: 36.0, left: buttonVerticalMargin, right: buttonVerticalMargin), buildRaisedButton(context, 'ЗАВЕРШИТЬ ПОКУПКУ', () => _purchase(context))),
buildButton(new EdgeInsets.only(top: 24.0, left: buttonVerticalMargin, right: buttonVerticalMargin), buildFlatButton(context, 'СКАНИРОВАТЬ', primaryColor))])
].reversed.toList()));
}
Widget getValueWithTitle(String title, String value) {
return new Container(padding: new EdgeInsets.only(left: verticalMargin, right: verticalMargin, top: 18.0),
child: new Column(children: <Widget>[
new Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[new Text(title, textAlign: TextAlign.left, style: new TextStyle(color: greyTextColor, fontSize: 14.0))]),
new Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[new Text(value, textAlign: TextAlign.left, style: new TextStyle(color: Colors.black, fontSize: 20.0))])
]));
@override Color getTextFilledBackground() {
return Colors.white;
}
Widget buildButton(EdgeInsets margin, Widget widget) {
return new Container(margin: margin, height: 48.0, child: new Row(children: <Widget>[new Expanded(child: widget)]));
}
Widget buildRaisedButton(BuildContext context, String text, VoidCallback onPressed) {
return new RaisedButton(child: new Text(text,
style: new TextStyle(color: Colors.white)),
onPressed: onPressed,
color: primaryColor);
/// Смена состояния экрана при изменении текста в поле ввода.
@override handleUserInput(String tmpString) {
setState(() {
tmpString = tmpString.replaceAll('-', '');
tmpString = tmpString.replaceAll(', ', '');
print(tmpString);
if (tmpString.contains('.')) {
int dotIndex = tmpString.indexOf('.');
integerPart = tmpString.substring(0, dotIndex);
fractionalPart = tmpString.substring(dotIndex + 1, tmpString.length);
if (fractionalPart.length > 2) {
fractionalPart = fractionalPart.substring(0, 2);
}
controller.text = '${integerPart}.${fractionalPart}';
} else {
integerPart = tmpString;
controller.text = tmpString;
}
textFieldValue = controller.text;
});
}
Widget buildFlatButton(BuildContext context, String title, Color textColor) {
return new Container(height: 48.0, child: new FlatButton(child: new Text(title,
style: new TextStyle(color: textColor)),
onPressed: () => startScanner(context)),
decoration: _getDecoraionForScanButton());
}
_buildSum() {
String temporaryInteger = integerPart;
String temporaryFractional = fractionalPart;
_getDecoraionForScanButton() {
return new BoxDecoration(
border: new Border.all(color: primaryColor, width: 1.0),
borderRadius: new BorderRadius.all(new Radius.circular(4.0)));
while (temporaryFractional.length < 2) {
temporaryFractional = temporaryFractional + '0';
}
return temporaryInteger + '.' + temporaryFractional;
}
_purchase(BuildContext context) {
String val = _buildSum();
showDialog(context: context, child: new AlertDialog(
title: new Text('Подтверждение'),
content: new Text('Вы подтверждаете покупку на ${_sum} руб?'),
content: new Text('Вы подтверждаете покупку на ${val} руб?'),
actions: <Widget>[
new FlatButton(
child: new Text('Нет'),
@@ -89,16 +95,11 @@ class PurchaseScreenState<T> extends BaseState<T> {
},
),
new FlatButton(
child: new Text('Дат'),
child: new Text('Да'),
onPressed: () {
pushRoute(context, new PurchaseSuccessScreen());
pushRoute(context, new PurchaseSuccessScreen(val));
},
)
]));
print('purchase');
}
_register(BuildContext context) async {
}
}

View File

@@ -7,10 +7,23 @@ import 'purchase.dart';
/// Экран проведения покупки.
class PurchaseSuccessScreen extends StatefulWidget {
@override State createState() => new _PurchaseSuccessScreenState();
String val = '';
PurchaseSuccessScreen(String val) {
this.val = val;
}
@override State createState() => new PurchaseSuccessScreenState(val);
}
class _PurchaseSuccessScreenState<T> extends PurchaseScreenState<T> {
class PurchaseSuccessScreenState<T> extends PurchaseScreenState<T> {
String val = '';
PurchaseSuccessScreenState(String val) {
this.val = val;
}
@override String getTitle() {
return "Проведение покупки";
@@ -25,17 +38,17 @@ class _PurchaseSuccessScreenState<T> extends PurchaseScreenState<T> {
@override Widget getScreenContent() {
return new Column(children: <Widget>[
getValueWithTitle('Покупатель', 'Знаменитый Рокер Паук'),
_getSuccessMessage(),
new Expanded(child: new Center()),
buildButton(new EdgeInsets.only(bottom: 74.0, left: 70.0, right: 70.0), buildRaisedButton(context, 'СКАНИРОВАТЬ', () => startScanner(context)))
]);
getValueWithTitle('Покупатель', 'Знаменитый Рокер Паук'),
getSuccessMessage(),
new Expanded(child: new Center()),
buildButton(new EdgeInsets.only(bottom: 74.0, left: buttonVerticalMargin, right: buttonVerticalMargin), buildRaisedButton(context, 'СКАНИРОВАТЬ', () => startScanner(context)))
]);
}
_getSuccessMessage() {
getSuccessMessage() {
return new Row(children: <Widget>[new Expanded(child: new Container(margin: new EdgeInsets.only(top: 20.0), height: 64.0,
decoration: new BoxDecoration(color: greenBackground),
child: new Center(child: new Text('Покупка на сумму 1234.00 руб. проведена', textAlign: TextAlign.center,
child: new Center(child: new Text('Покупка на сумму ${val} руб. проведена', textAlign: TextAlign.center,
style: new TextStyle(fontWeight: FontWeight.bold, color: tokenActiveTextColor)))))]);
}

View File

@@ -21,7 +21,7 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
}
@override getHint() {
return 'ID merchant';
return 'ID магазина';
}
@overide getMenuButtons(BuildContext context) {
@@ -37,35 +37,17 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
getLogo(),
getHintLabel(),
getDecoratedTextWidget(),
_getButton(context)]))
buildButton(new EdgeInsets.only(top: 36.0, left: buttonVerticalMargin, right: buttonVerticalMargin),
buildRaisedButton(context, 'ЗАРЕГИСТРИРОВАТЬ', _isValidMerchantID() && !loading ? () => _registerShop(context) : null))]))
].reversed.toList()));
}
/// Метод возвращает кнопку, которая запускает отправку токена кассы на сервер.
_getButton(BuildContext context) {
double buttonHeight = 42.0;
double topMargin = 36.0;
double horizontalPadding = 40.0; // Отступы по краям от кнопки.
return new Container(margin: new EdgeInsets.only(top: topMargin), height: buttonHeight,
padding: new EdgeInsets.only(left: horizontalPadding, right: horizontalPadding),
child: new RaisedButton(child: new Text('ЗАРЕГИСТРИРОВАТЬ',
style: new TextStyle(color: Colors.white)),
onPressed: _isValidMerchantID() && !loading ? () => _registerShop(context) : null,
color: primaryColor));
}
/// Токен кассы - это DIN код. DIN код - это специальный код динекта, максимальная его длина - 25 символов.
_isValidMerchantID() {
print("${textFieldValue.length}");
return textFieldValue.length > 0 && textFieldValue.length < 25;
}
/// Смена состояния экрана при изменении текста в поле ввода.
_handleUserInput(String text) {
setState(() {
textFieldValue = text;
});
}
/// Показать индикатор, запросить токен.
_registerShop(BuildContext context) {
setState(() {
@@ -84,7 +66,6 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
/// Получение от платформы id установки, формирование запроса на получение токена, сохранение токена.
_register(BuildContext context) async {
const platform = const MethodChannel('com.dinect.checker/instance_id');
String url = intUrl + 'tokens/?_dmapptoken=' + intToken;
String pos = (new DateTime.now().millisecondsSinceEpoch / 1000).toString();
@@ -95,31 +76,33 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
'pos': pos,
};
httpClient.post(url, body: body).then((response) {
pushRoute(context, new FinishRegistrationScreen());
// httpClient.post(url, body: body).then((response) {
setState(() {
error = null;
});
print(response.body);
Map parsedMap = JSON.decode(response.body);
setState(() {
loading = false;
});
if (response.statusCode == 201) {
token = parsedMap['token'];
platform.invokeMethod('saveToken', {'token' : token});
platform.invokeMethod('saveMerchantID', {'merchantID' : merchantID});
pushRoute(context, new FinishRegistrationScreen());
} else {
setState(() {
error = parsedMap['errors'][0];
});
}
}).catchError((error) {
setState(() {
error = 'Отсутствует интернет соединение';
});
});
// print(response.body);
// Map parsedMap = JSON.decode(response.body);
// setState(() {
// loading = false;
// });
// if (response.statusCode == 201) {
// token = parsedMap['token'];
// platform.invokeMethod('saveToken', {'token' : token});
// platform.invokeMethod('saveMerchantID', {'merchantID' : merchantID});
// pushRoute(context, new FinishRegistrationScreen());
// } else {
// setState(() {
// error = parsedMap['errors'][0];
// });
// }
// }).catchError((error) {
// setState(() {
// error = 'Отсутствует интернет соединение';
// });
// });
}
}

View File

@@ -30,6 +30,7 @@ class SplashScreen extends StatelessWidget {
const platform = const MethodChannel('com.dinect.checker/instance_id');
token = await platform.invokeMethod('getToken');
print('token: $token');
// В случае, если в приложении отсутствует токен,
// необходимо запустить регистрацию кассы.
@@ -44,7 +45,6 @@ class SplashScreen extends StatelessWidget {
bool active = parsedMap['active'];
if (active) {
Navigator.of(context).pop();
// Запускается экран сканера, токен кассы активирован, с его помощью можно делать запросы к pos-api.
startScanner(context);
} else {