diff --git a/android/app/src/main/java/com/dinect/checker/MainActivity.java b/android/app/src/main/java/com/dinect/checker/MainActivity.java
index 8566293..be9a587 100644
--- a/android/app/src/main/java/com/dinect/checker/MainActivity.java
+++ b/android/app/src/main/java/com/dinect/checker/MainActivity.java
@@ -38,6 +38,7 @@ public class MainActivity extends FlutterActivity {
private MethodChannel mChannel;
private Map mScannerArgs;
+ private Result scannerResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -59,6 +60,7 @@ public class MainActivity extends FlutterActivity {
result.success(BuildConfig.currency);
break;
case "startScanner":
+ scannerResult = result;
startScannerActivity(call);
break;
case "isOnline":
@@ -169,7 +171,7 @@ public class MainActivity extends FlutterActivity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == START_SCANNER_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
- finish();
+ scannerResult.error("Scanning is cancelled", null, null);
} else if (resultCode == RESULT_OK) {
if (data != null && data.getExtras() != null) {
String user = data.getExtras().getString("user", null);
@@ -178,7 +180,9 @@ public class MainActivity extends FlutterActivity {
ArrayList args = new ArrayList<>(2);
args.add(user);
args.add(card);
- mChannel.invokeMethod("purchase", args);
+ if(scannerResult != null) {
+ scannerResult.success(args);
+ }
} else {
String menuItem = data.getExtras().getString("item", null);
if (menuItem != null) {
diff --git a/assets/values-en/strings.xml b/assets/values-en/strings.xml
index 5b7b782..f278bbe 100644
--- a/assets/values-en/strings.xml
+++ b/assets/values-en/strings.xml
@@ -153,4 +153,7 @@ Set on switch on Camera
Go back to %s.
Open settings
+ Cancel
+ Are you sure you want to cancel this purchase?
+ Total amount: %s %s, with discount of %s %s (%s%%)
diff --git a/assets/values-es/strings.xml b/assets/values-es/strings.xml
index 1fa7283..7a9806e 100644
--- a/assets/values-es/strings.xml
+++ b/assets/values-es/strings.xml
@@ -149,4 +149,5 @@ Establecer en el interruptor de la cámara
Vuelve a %s
Abre las configuraciones
+ Cancelar
diff --git a/assets/values-ru/strings.xml b/assets/values-ru/strings.xml
index a6d0f16..91a4437 100644
--- a/assets/values-ru/strings.xml
+++ b/assets/values-ru/strings.xml
@@ -152,4 +152,6 @@
Вернитесь к приложению %s.
Открыть настройки
+ Отмена
+ Вы уверены что хотите отменить покупку?
diff --git a/assets/values-ua/strings.xml b/assets/values-ua/strings.xml
index 6e70247..510ae3c 100644
--- a/assets/values-ua/strings.xml
+++ b/assets/values-ua/strings.xml
@@ -154,4 +154,5 @@
Поверніться до додатка %s.
Відкрити параметри
+ Скасувати
diff --git a/ios/Runner/ScannerViewController.swift b/ios/Runner/ScannerViewController.swift
index e41cbcf..2eb7101 100644
--- a/ios/Runner/ScannerViewController.swift
+++ b/ios/Runner/ScannerViewController.swift
@@ -272,7 +272,7 @@ import ZXingObjC
} else {
print("Result is not nil (ios code)");
self.dismiss(animated: true) {
- self.platformChannel?.invokeMethod("purchase", arguments: [result, str])
+ self.platformChannel?.invokeMethod("scanSuccess", arguments: [result!, str])
}
}
})
diff --git a/lib/base/base_state.dart b/lib/base/base_state.dart
index be78f8a..83971af 100644
--- a/lib/base/base_state.dart
+++ b/lib/base/base_state.dart
@@ -10,7 +10,6 @@ import 'package:checker/screens/faq.dart';
import 'package:checker/strings.dart';
import 'package:checker/db.dart';
import 'package:flutter/rendering.dart';
-import 'package:meta/meta.dart';
abstract class BaseState extends State {
diff --git a/lib/screens/finish_registration.dart b/lib/screens/finish_registration.dart
index e9435e5..6cc42ef 100644
--- a/lib/screens/finish_registration.dart
+++ b/lib/screens/finish_registration.dart
@@ -1,5 +1,4 @@
import 'dart:convert';
-import 'dart:async';
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/base_state.dart';
diff --git a/lib/screens/purchase.dart b/lib/screens/purchase.dart
index 5e0c20e..9b0b54c 100644
--- a/lib/screens/purchase.dart
+++ b/lib/screens/purchase.dart
@@ -1,5 +1,6 @@
import 'package:checker/base/base_screen.dart';
import 'package:checker/db.dart';
+import 'package:checker/screens/purchase_sum.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:convert';
@@ -14,13 +15,16 @@ import 'package:checker/network.dart';
import 'package:checker/base/base_state.dart';
import 'package:checker/screens/purchase_success.dart';
+typedef PurchaseResponseCallback = void Function(Map, String, String);
+
/// Экран проведения покупки.
class PurchaseScreen extends BaseScreen {
- PurchaseScreen(helper, app, this.user, this.card) : super(helper, app);
+ PurchaseScreen(helper, app, this.user, this.card, this.sum) : super(helper, app);
final String user;
final String card;
+ final String sum;
@override
State createState() => new PurchaseScreenState(helper, app, user, card);
@@ -30,7 +34,7 @@ class PurchaseScreenState extends BaseState {
/// Объект, помогающий вручную изменять введенный пользователем текст.
/// Используется для форматирования введенных пользователем данных
/// (удаляет запрещенные символы до их отображаения).
- TextEditingController controller = new TextEditingController();
+ TextEditingController controller;
TextEditingController bonusController = new TextEditingController();
@@ -43,6 +47,7 @@ class PurchaseScreenState extends BaseState {
@override
void initState() {
+ controller = new TextEditingController(text: widget.sum ?? "");
loading = true;
requestAsyncData(user);
buildFocusNode();
@@ -116,9 +121,9 @@ class PurchaseScreenState extends BaseState {
widgetList.add(wrapButton(
getScreenMargins(24.0),
- getScanButton(
+ getCancelScanButton(
context,
- StringsLocalization.scan(),
+ StringsLocalization.cancel(),
Resources.getPrimaryColor(app)
)
));
@@ -185,12 +190,12 @@ class PurchaseScreenState extends BaseState {
StringsLocalization.completePurchase(), () => onPurchaseClick());
}
- Widget getScanButton(BuildContext context, String title, Color textColor) {
+ Widget getCancelScanButton(BuildContext context, String title, Color textColor) {
return new Container(
height: buttonHeight,
child: new FlatButton(
child: new Text(title, style: new TextStyle(color: textColor)),
- onPressed: () => restartScanner()),
+ onPressed: () => restartFlow()),
decoration: new BoxDecoration(
border: new Border.all(
color: Resources.getButtonColor(app), width: 1.0),
@@ -249,8 +254,8 @@ class PurchaseScreenState extends BaseState {
loading = false;
this.coupons = coupons['results'];
this.loyalityType = loyality['type'];
- setBonuses(loyality, showBonus);
});
+ setBonuses(loyality, showBonus);
}
}
@@ -293,16 +298,26 @@ class PurchaseScreenState extends BaseState {
}
onPurchaseClick() {
+ String val = _parseSum(controller.text);
+ purchase(val, false, _showPrecalculatedValues);
+ }
+
+ void _showPrecalculatedValues(Map response, String sumTotal, String token) {
String val = _parseSum(controller.text);
helper.getCurrency().then((currency) {
print(currency.toString());
+ final String totalAmount = response['sum_total'];
+ final String totalDiscount = response['sum_discount'];
+ final String discount = (response['discount'] as int).toString();
+ print(response);
showDialog(
context: context,
- builder: (_) => new AlertDialog(
+ builder: (_) =>
+ new AlertDialog(
title: new Text(StringsLocalization.confirmation()),
- content:
- new Text(
- StringsLocalization.confirmPurchase(val, currency)
+ content: Text(
+ StringsLocalization.purchaseDetails(totalAmount,
+ totalDiscount, discount, currency)
),
actions: [
new FlatButton(
@@ -315,13 +330,42 @@ class PurchaseScreenState extends BaseState {
child: new Text(StringsLocalization.yes()),
onPressed: () {
Navigator.of(context).pop();
- purchase(val);
+ purchase(val, true, _paymentConfirmed);
},
)
]));
});
}
+ void _paymentConfirmed(Map purchase, String sumTotal, String token) async {
+ var couponsResponse;
+
+ try {
+ couponsResponse = await getCouponsRequest(purchase['coupons_url'], token);
+ print(couponsResponse.body);
+ } catch(error) {
+ purchaseInProgress = false;
+ print(error.toString());
+ }
+
+ Map coupons = json.decode(couponsResponse.body);
+
+ new Future.delayed(const Duration(milliseconds: 200), () {
+ print('show purchase success!');
+ var route = new MaterialPageRoute(builder: (BuildContext context) => new PurchaseSuccessScreen(
+ sumTotal,
+ user['first_name'] == null ? '' : user['first_name'],
+ helper,
+ app,
+ purchase,
+ coupons['results']
+ ), fullscreenDialog: true);
+ Navigator.of(context).push(route).then((token) {
+ Navigator.of(context).pop(token);
+ });
+ });
+ }
+
apiErrorAlert(String errorText) {
showDialog(
context: context,
@@ -339,13 +383,13 @@ class PurchaseScreenState extends BaseState {
);
}
- purchase(String sumTotal) async {
+ purchase(String sumTotal, bool commit, PurchaseResponseCallback callback) async {
setState(() {
loading = true;
});
if (await platform.invokeMethod('isOnline')) {
if (!purchaseInProgress) {
- purchaseInProgress = true;
+ purchaseInProgress = commit;
String token = await helper.getToken();
var result = await helper.getMerchantID();
@@ -361,10 +405,13 @@ class PurchaseScreenState extends BaseState {
var body = {
'doc_id': result,
'curr_iso_code': currency.toString(),
- 'commit': 'true',
'sum_total': sumTotal,
};
+ if(commit) {
+ body['commit'] = 'true';
+ }
+
if (bonusController.text.length > 0) {
body['bonus_payment'] = bonusController.text;
}
@@ -392,32 +439,7 @@ class PurchaseScreenState extends BaseState {
purchaseInProgress = false;
apiErrorAlert(errors[0]);
} else {
- var couponsResponse;
-
- try {
- couponsResponse = await getCouponsRequest(purchase['coupons_url'], token);
- print(couponsResponse.body);
- } catch(error) {
- purchaseInProgress = false;
- print(error.toString());
- }
-
- Map coupons = json.decode(couponsResponse.body);
-
- new Future.delayed(const Duration(milliseconds: 200), () {
- print('show purchase success!');
- var route = new MaterialPageRoute(builder: (BuildContext context) => new PurchaseSuccessScreen(
- sumTotal,
- user['first_name'] == null ? '' : user['first_name'],
- helper,
- app,
- purchase,
- coupons['results']
- ), fullscreenDialog: true);
- Navigator.of(context).push(route).then((token) {
- Navigator.of(context).pop(token);
- });
- });
+ callback(purchase, sumTotal, token);
}
}
}
@@ -426,24 +448,47 @@ class PurchaseScreenState extends BaseState {
void setBonuses(Map bonuses, bool showBonus) {
print('loyalityType ' + this.loyalityType);
if (bonuses['type'] == 'amount') {
- this.loyalty = '${user['discount']}%';
+ setState(() => this.loyalty = '${user['discount']}%');
} else {
double loyaltyVal = (double.parse(bonuses['amount_to_bonus'][1]) /
bonuses['amount_to_bonus'][0]) * 100;
- this.loyalty = '${loyaltyVal.toStringAsFixed(0)}%';
+ setState(() => this.loyalty = '${loyaltyVal.toStringAsFixed(0)}%');
}
if (showBonus && (this.loyalityType == 'bonus')) {
- this.bonus = '${user['bonus']}';
+ setState(() => this.bonus = '${user['bonus']}');
}
print('loyalty ' + this.loyalty);
print('bonus ' + this.bonus);
}
- restartScanner() {
- helper.getToken().then((token) {
- Navigator.of(context).pop(token);
- });
+ restartFlow() {
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ content: Text(StringsLocalization.cancelDialog()),
+ actions: [
+ new FlatButton(
+ child: new Text(StringsLocalization.no()),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ new FlatButton(
+ child: new Text(StringsLocalization.yes()),
+ onPressed: () {
+ Navigator.of(context).pop();
+ helper.getToken().then((token) {
+ Navigator.pushAndRemoveUntil(context,
+ MaterialPageRoute(builder: (context) =>
+ PurchaseSumScreen(widget.helper, widget.app, token)),
+ (route) => false);
+ });
+ },
+ )
+ ],
+ )
+ );
}
FocusNode bonusFocusNode = new FocusNode();
@@ -456,25 +501,14 @@ class PurchaseScreenState extends BaseState {
sumFocusNode.addListener(() {
setState(() {
-
- if (sumFocusNode.hasFocus && bonusFocusNode.hasFocus) {
- bonusFocusNode.unfocus();
- }
-
if (sumFocusNode.hasFocus) {
- scrollController.animateTo(pos, duration: new Duration(seconds: 1), curve: Curves.ease);
+ sumFocusNode.unfocus();
}
-
});
});
bonusFocusNode.addListener(() {
setState(() {
-
- if (bonusFocusNode.hasFocus && sumFocusNode.hasFocus) {
- sumFocusNode.unfocus();
- }
-
if (bonusFocusNode.hasFocus) {
scrollController.animateTo(pos, duration: new Duration(seconds: 1), curve: Curves.ease);
}
diff --git a/lib/screens/purchase_success.dart b/lib/screens/purchase_success.dart
index 16ff417..bd7b490 100644
--- a/lib/screens/purchase_success.dart
+++ b/lib/screens/purchase_success.dart
@@ -2,6 +2,7 @@ import 'package:checker/base/base_state.dart';
import 'package:checker/common.dart';
import 'package:checker/consts.dart';
import 'package:checker/db.dart';
+import 'package:checker/screens/purchase_sum.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
@@ -135,7 +136,11 @@ class PurchaseSuccessScreenState extends BaseState {
getScanButton() {
String title = StringsLocalization.scan();
- return buildRaisedButton(title, () => Navigator.of(context).pop(token));
+ return buildRaisedButton(title,
+ () => Navigator.pushAndRemoveUntil(context,
+ MaterialPageRoute(builder: (context) =>
+ PurchaseSumScreen(widget.helper, widget.app, token)),
+ (route) => false));
}
diff --git a/lib/screens/purchase_sum.dart b/lib/screens/purchase_sum.dart
new file mode 100644
index 0000000..d507063
--- /dev/null
+++ b/lib/screens/purchase_sum.dart
@@ -0,0 +1,202 @@
+import 'dart:convert';
+
+import 'package:checker/base/base_screen.dart';
+import 'package:checker/db.dart';
+import 'package:checker/screens/purchase.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:http/http.dart';
+import 'dart:core';
+
+import 'package:checker/resources.dart';
+import 'package:checker/strings.dart';
+import 'package:checker/common.dart';
+import 'package:checker/consts.dart';
+import 'package:checker/base/base_state.dart';
+import 'package:checker/network.dart';
+
+/// Экран ввода суммы покупки
+class PurchaseSumScreen extends BaseScreen {
+ PurchaseSumScreen(helper, app, this.token) : super(helper, app);
+
+ final String token;
+
+ @override
+ State createState() =>
+ new PurchaseSumScreenState(helper, app);
+}
+
+class PurchaseSumScreenState extends BaseState {
+ TextEditingController _sumController = new TextEditingController();
+
+ bool isAutomaticallyImplyLeading() => false;
+
+ PurchaseSumScreenState(SqliteHelper helper, String app) : super(helper, app);
+
+ @override
+ void initState() {
+ super.initState();
+ _subscribe();
+ }
+
+ @override
+ Widget build(BuildContext ctx) {
+ return getMainWidget();
+ }
+
+ @override
+ Widget getScreenContent() => Column(
+ children: [
+ getHintLabel(),
+ getInputField(),
+ wrapButton(
+ _getScreenMargins(24.0),
+ getScanButton(context, StringsLocalization.scan(),
+ Resources.getPrimaryColor(app)))
+ ],
+ );
+
+ EdgeInsets _getScreenMargins(double top) {
+ double side = 42.0;
+ return new EdgeInsets.only(top: top, left: side, right: side);
+ }
+
+ Widget getScanButton(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()),
+ decoration: new BoxDecoration(
+ border: new Border.all(
+ color: Resources.getButtonColor(app), width: 1.0),
+ borderRadius: new BorderRadius.all(new Radius.circular(4.0))));
+ }
+
+ void _startScanner() {
+ platform.invokeMethod('getEndpoint').then((url) {
+ platform.invokeMethod('getAppToken').then((appToken) {
+ Map args = StringsLocalization.strings;
+ args['token'] = widget.token;
+ args['url'] = url;
+ args['appToken'] = appToken;
+ args['localeCode'] = StringsLocalization.localeCode;
+ args['color'] = Resources.getPrimaryColor(app).value.toString();
+ platform.invokeMethod('startScanner', args).then((result) {
+ _processResult(result);
+ });
+ });
+ });
+ }
+
+ @override
+ String getTitle() {
+ return StringsLocalization.sum();
+ }
+
+ @override
+ getHintString() {
+ return StringsLocalization.sum();
+ }
+
+ @override
+ getTextWidget() {
+ return new TextField(
+ keyboardType: TextInputType.number,
+ decoration: new InputDecoration.collapsed(
+ hintText: getHintString(),
+ hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
+ controller: _sumController,
+ onSubmitted: (String text) {
+ setState(() {
+ _sumController.text = _parseSum(text);
+ });
+ },
+ textAlign: TextAlign.center);
+ }
+
+ String _cleanupNumber(String text) {
+ String tmp = text
+ .replaceAll(' ', '')
+ .replaceAll('-', '')
+ .replaceAll(',', '.')
+ .replaceAll('..', '.');
+
+ while (tmp.indexOf('..') != -1) {
+ tmp = tmp.replaceAll('..', '.');
+ }
+ return tmp;
+ }
+
+ String _parseSum(String input) {
+ num sumTotal = 0.0;
+ String text = _cleanupNumber(input);
+
+ try {
+ sumTotal = num.parse(text);
+ } catch (exception) {
+ print(exception);
+ try {
+ int idx = text.indexOf('.');
+ String integerPart = text.substring(0, idx);
+ String fractionalPart = text.substring(idx + 1, text.length);
+ if (fractionalPart.length > 2) {
+ fractionalPart = fractionalPart.substring(0, 2);
+ }
+ return '$integerPart.$fractionalPart';
+ } catch (exception) {
+ print(exception);
+ }
+ }
+ print(sumTotal.toStringAsFixed(2));
+ return sumTotal.toStringAsFixed(2);
+ }
+
+ void _processResult(result) {
+ if (result is List) {
+ final String user = result[0] as String;
+ final String card = result[1] as String;
+ Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
+ String sum = _parseSum(_sumController.text);
+ _sumController.text = "";
+ return PurchaseScreen(helper, app, user, card, sum);
+ }));
+ }
+ }
+
+ void _subscribe() async {
+ platform.setMethodCallHandler((MethodCall call) async {
+ if (call.method == 'findUser') {
+ try {
+ Response userResponse;
+
+ switch (call.arguments[1]) {
+ case 'card':
+ userResponse = await getUserByCard(call.arguments[0], widget.token);
+ break;
+ case 'phone':
+ userResponse = await getUserByPhone(call.arguments[0], widget.token);
+ break;
+ }
+
+ if (userResponse != null) {
+ print('I have user in method handler!');
+ List users = json.decode(userResponse.body);
+ if (users.length > 0) {
+ return json.encode(users[0]);
+ } else {
+ throw new FlutterError("Users not found");
+ }
+ } else {
+ throw new FlutterError("Users not found");
+ }
+ } catch (error) {
+ print(error.toString());
+ throw new FlutterError("Users not found");
+ }
+ } else if (call.method == 'scanSuccess') {
+ _processResult(call.arguments);
+ }
+ });
+ }
+}
diff --git a/lib/screens/splash.dart b/lib/screens/splash.dart
index c678419..5fdec4e 100644
--- a/lib/screens/splash.dart
+++ b/lib/screens/splash.dart
@@ -11,6 +11,7 @@ import 'package:checker/resources.dart';
import 'package:checker/screens/faq.dart';
import 'package:checker/screens/finish_registration.dart';
import 'package:checker/screens/purchase.dart';
+import 'package:checker/screens/purchase_sum.dart';
import 'package:checker/screens/registration.dart';
import 'package:checker/screens/settings.dart';
import 'package:checker/strings.dart';
@@ -241,23 +242,13 @@ class _SplashScreenState extends BaseState {
: json.encode(call.arguments[0]);
print(userString);
String card = call.arguments[1];
- showNextScreen(new PurchaseScreen(helper, app, userString, card));
+ showNextScreen(new PurchaseScreen(helper, app, userString, card, null));
}
});
- platform.invokeMethod('getEndpoint').then((url) {
- platform.invokeMethod('getAppToken').then((appToken) {
- Map args = StringsLocalization.strings;
- args['token'] = token;
- args['url'] = url;
- args['appToken'] = appToken;
- args['localeCode'] = StringsLocalization.localeCode;
- args['color'] = Resources
- .getPrimaryColor(app)
- .value
- .toString();
- platform.invokeMethod('startScanner', args);
- });
- });
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(builder: (context) =>
+ PurchaseSumScreen(helper, app, token))
+ );
}
}
diff --git a/lib/strings.dart b/lib/strings.dart
index 026c2a7..ec5b3c4 100644
--- a/lib/strings.dart
+++ b/lib/strings.dart
@@ -111,17 +111,25 @@ class StringsLocalization {
return [nominative, singular, plural];
}
+ static _normalizeDouble(String val) => val.substring(0, val.length - 3);
static String confirmPurchase(String val, int code) {
- String trimmedVal = val.substring(0, val.length - 3);
+ String trimmedVal = _normalizeDouble(val);
return sprintf(strings['confirm_purchase'], [val, declineCurrency(int.parse(trimmedVal), code)]);
}
static String purchaseCompleted(String val, int code) {
- String trimmedVal = val.substring(0, val.length - 3);
+ String trimmedVal = _normalizeDouble(val);
return sprintf(strings['purchase_complite'], [val, declineCurrency(int.parse(trimmedVal), code)]);
}
+ static String purchaseDetails(String total, String discountTotal, String discount, int code) {
+ final String normTotal = _normalizeDouble(total);
+ final String normDiscountTotal = _normalizeDouble(discountTotal);
+ return sprintf(strings['purchase_details'], [total, declineCurrency(int.parse(normTotal), code),
+ discountTotal, declineCurrency(int.parse(normDiscountTotal), code), discount]);
+ }
+
static String registration() => strings['registration'];
static String usage() => strings['usage'];
static String support() => strings['support'];
@@ -214,4 +222,6 @@ class StringsLocalization {
static String joysMinus() => strings['joys_minus'];
static String joysHint() => strings['joys_hint'];
static String phone() => strings['phone'];
+ static String cancel() => strings['cancel'];
+ static String cancelDialog() => strings['purchase_cancellation'];
}