merge with 12226_return_purchase

This commit is contained in:
vtretyakov
2019-04-09 23:00:19 +07:00
13 changed files with 309 additions and 4 deletions

BIN
assets/return.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -154,4 +154,12 @@ Set on switch on Camera
Go back to %s. Go back to %s.
</string> </string>
<string name="open_settings">Open settings</string> <string name="open_settings">Open settings</string>
<string name="returnLabel">Return</string>
<string name="return_purchase_content">Are you sure you want to return purchase, which cost %s on %s?
You cannot undo this.
You bonus balance will be changed and you will have %s bonus points.</string>
<string name="return_confirmation">Return confirmed</string>
<string name="return_confirmation_content">Purchase, which cost %s on %s is returned.</string>
<string name="points">Points</string>
</resources> </resources>

View File

@@ -23,7 +23,7 @@
<string name="confirmation">Confirmacón</string> <string name="confirmation">Confirmacón</string>
<string name="no">No</string> <string name="no">No</string>
<string name="purchase_complite">Compra por %s %s está realizada</string> <string name="purchase_complite">Compra por %s %s está realizada</string>
<string name="payment_complite">The payment of %s %s was completed</string> <string name="payment_complite">Se completó el pago de %s %s</string>
<string name="registration">Registro</string> <string name="registration">Registro</string>
<string name="usage">Explotación</string> <string name="usage">Explotación</string>
<string name="support">Contactos del soporte técnico</string> <string name="support">Contactos del soporte técnico</string>
@@ -150,4 +150,12 @@ Establecer en el interruptor de la cámara
Vuelve a %s Vuelve a %s
</string> </string>
<string name="open_settings">Abre las configuraciones</string> <string name="open_settings">Abre las configuraciones</string>
<string name="returnLabel">Regreso</string>
<string name="return_purchase_content">¿Está seguro de que desea devolver la compra, que costó %s en %s
No puedes deshacer esto.
Se cambiará su saldo de bonificación y tendrá %s puntos de bonificación.</string>
<string name="return_confirmation">Regreso confirmado</string>
<string name="return_confirmation_content">Compra, cuyo costo %s en %s se devuelve.</string>
<string name="points">Puntos</string>
</resources> </resources>

View File

@@ -153,4 +153,12 @@
Вернитесь к приложению %s. Вернитесь к приложению %s.
</string> </string>
<string name="open_settings">Открыть настройки</string> <string name="open_settings">Открыть настройки</string>
<string name="returnLabel">Возврат</string>
<string name="return_purchase_content">Вы подтверждаете возврат покупки на сумму %s от %s?
Отменить возврат нельзя.
Бонусный баланс будет изменен и составит %s бонусов.</string>
<string name="return_confirmation">Возврат подтвержден</string>
<string name="return_confirmation_content">Покупка на сумму %s от %s возвращена.</string>
<string name="points">Баллы</string>
</resources> </resources>

View File

@@ -23,7 +23,7 @@
<string name="confirmation">Підтвердження</string> <string name="confirmation">Підтвердження</string>
<string name="no">Ні</string> <string name="no">Ні</string>
<string name="purchase_complite">Купівля на суму %s %s проведена</string> <string name="purchase_complite">Купівля на суму %s %s проведена</string>
<string name="payment_complite">The payment of %s %s was completed</string> <string name="payment_complite">Виплату %s %s було завершено</string>
<string name="registration">Реєстрація</string> <string name="registration">Реєстрація</string>
<string name="usage">Використання</string> <string name="usage">Використання</string>
<string name="support">Контакти підтримки</string> <string name="support">Контакти підтримки</string>
@@ -155,4 +155,12 @@
Поверніться до додатка %s. Поверніться до додатка %s.
</string> </string>
<string name="open_settings">Відкрити параметри</string> <string name="open_settings">Відкрити параметри</string>
<string name="returnLabel">Повернення</string>
<string name="return_purchase_content">Ви дійсно хочете повернути покупку, яка коштує %s на %s?
Ви не можете скасувати це.
Баланс бонусів буде змінено, і ви отримаєте %s бонусний бал.</string>
<string name="return_confirmation">Повернення підтверджено</string>
<string name="return_confirmation_content">Повертається придбання, вартість якого %s на %s.</string>
<string name="points">Бонусні бали</string>
</resources> </resources>

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:checker/resources.dart'; import 'package:checker/resources.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -10,7 +11,6 @@ import 'package:checker/screens/faq.dart';
import 'package:checker/strings.dart'; import 'package:checker/strings.dart';
import 'package:checker/db.dart'; import 'package:checker/db.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
abstract class BaseState<T extends StatefulWidget> extends State<T> { abstract class BaseState<T extends StatefulWidget> extends State<T> {
@@ -78,7 +78,14 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
child: getMenuItem(help_png, StringsLocalization.help()) child: getMenuItem(help_png, StringsLocalization.help())
)); ));
if (Theme.of(context).platform != TargetPlatform.iOS) { if(showReturnScreen()) {
menuItemList.add(new PopupMenuItem(
value: 3,
child: getMenuItem(return_png, StringsLocalization.returnLabel())
));
}
if (Platform.isAndroid) {
menuItemList.add(new PopupMenuItem( menuItemList.add(new PopupMenuItem(
value: 2, value: 2,
child: getMenuItem(exit_png, StringsLocalization.exit()) child: getMenuItem(exit_png, StringsLocalization.exit())
@@ -95,6 +102,8 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
]; ];
} }
bool showReturnScreen() => false;
void onOptionsItemClick(int index) { void onOptionsItemClick(int index) {
switch (index) { switch (index) {
case 0: { case 0: {
@@ -119,9 +128,14 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
platform.invokeMethod('finish'); platform.invokeMethod('finish');
break; break;
} }
case 3:
openReturnScreen();
break;
} }
} }
void openReturnScreen() {}
/// Возвращает пункт меню (Картинка с текстом) /// Возвращает пункт меню (Картинка с текстом)
Widget getMenuItem(String image, String text) { Widget getMenuItem(String image, String text) {
return new Row(children: [ return new Row(children: [

View File

@@ -7,6 +7,7 @@ const String exit_png = 'assets/exit.png';
const String help_png = 'assets/help.png'; const String help_png = 'assets/help.png';
const String settings_png = 'assets/settings.png'; const String settings_png = 'assets/settings.png';
const String settings_arrow_png = 'assets/settings_arrow.png'; const String settings_arrow_png = 'assets/settings_arrow.png';
const String return_png = 'assets/return.png';
const String check_png = 'assets/check.png'; const String check_png = 'assets/check.png';
const String activate_token_bg_png = 'assets/activate_token_message_background.png'; const String activate_token_bg_png = 'assets/activate_token_message_background.png';
const String active_token_bg_png = 'assets/active_token_message_background.png'; const String active_token_bg_png = 'assets/active_token_message_background.png';

View File

@@ -106,3 +106,33 @@ getEndpoint() async {
getToken() async { getToken() async {
return await platform.invokeMethod('getAppToken'); return await platform.invokeMethod('getAppToken');
} }
Future<Response> getUserPurchases(String token, int userId, int page) async {
var headers = {
'DM-Authorization': 'dmapptoken ${await getToken()}',
'Authorization': 'dmtoken $token',
'Accept-Language': StringsLocalization.localeCode
};
var finalEndpoint = "${await getEndpoint()}users/$userId/purchases/?returned=false&sort=desc&page=$page";
print(finalEndpoint);
return httpClient.get(finalEndpoint, headers: headers);
}
Future<Response> returnPurchase(String token, int userId, int purchaseId) async {
var headers = {
'DM-Authorization': 'dmapptoken ${await getToken()}',
'Authorization': 'dmtoken $token',
'Accept-Language': StringsLocalization.localeCode
};
var finalEndpoint = "${await getEndpoint()}users/$userId/purchases/$purchaseId";
print(finalEndpoint);
return httpClient.delete(finalEndpoint, headers: headers);
}

View File

@@ -1,5 +1,6 @@
import 'package:checker/base/base_screen.dart'; import 'package:checker/base/base_screen.dart';
import 'package:checker/db.dart'; import 'package:checker/db.dart';
import 'package:checker/screens/return.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'dart:convert'; import 'dart:convert';
@@ -127,6 +128,16 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
return listView; return listView;
} }
bool showReturnScreen() => true;
void openReturnScreen() {
Future.delayed(const Duration(milliseconds: 200), () {
Route route = MaterialPageRoute<String>(builder: (BuildContext context) =>
ReturnScreen(helper, app, user['id'], user['bonus']), fullscreenDialog: true);
Navigator.push(context, route);
});
}
getBonusInputField() { getBonusInputField() {
var bonusTextField = new TextField( var bonusTextField = new TextField(
keyboardType: TextInputType.number, keyboardType: TextInputType.number,

200
lib/screens/return.dart Normal file
View File

@@ -0,0 +1,200 @@
import 'dart:convert';
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/base_state.dart';
import 'package:checker/db.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
import 'package:checker/network.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
class ReturnScreen extends BaseScreen {
final int userId;
final int bonus;
ReturnScreen(SqliteHelper helper, String app, this.userId, this.bonus) : super(helper, app);
@override
_ReturnScreenState createState() => _ReturnScreenState<ReturnScreen>(helper, app);
}
class _ReturnScreenState<T> extends BaseState<ReturnScreen> {
static DateFormat dateFormat = DateFormat('d MMM yy');
ScrollController _controller;
List _purchases = [];
int _bonus;
int _page = 1;
bool _fullLoaded = false;
bool _loading = false;
_ReturnScreenState(SqliteHelper helper, String app) : super(helper, app);
bool isAutomaticallyImplyLeading() => true;
@override
Widget getScreenContent() => ListView.separated(
itemBuilder: (context, index) {
final TextStyle style = Theme.of(context).textTheme.subhead;
if(index > 0) {
final item = _purchases[index - 1];
final DateTime date = DateTime.parse(item['date']);
final String formattedDate = dateFormat.format(date);
return GestureDetector(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: <Widget>[
Expanded(
child: Text(formattedDate, style: style),
),
Expanded(
child: Text(item['sum_total'], style: style),
),
Expanded(
child: Text(item['sum_bonus'].toString(), style: style),
)
],
),
),
onTap: () => _confirmReturnPurchase(item, formattedDate),
);
} else {
final TextStyle headerStyle = style.copyWith(color: Colors.grey);
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: <Widget>[
Expanded(
child: Container(),
),
Expanded(
child: Text(StringsLocalization.sum(), style: headerStyle),
),
Expanded(
child: Text(StringsLocalization.points(), style: headerStyle),
)
],
),
);
}
},
separatorBuilder: (_, index) => index == 0
? Container()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Divider(height: 1,),
),
itemCount: _purchases.length > 0 ? _purchases.length + 1 : _purchases.length,
controller: _controller);
@override
Widget build(BuildContext context) => getMainWidget();
@override
String getTitle() => StringsLocalization.returnLabel();
@override
void initState() {
super.initState();
setState(() {
loading = true;
});
_loadUserPurchases();
_bonus = widget.bonus;
_controller = ScrollController();
_controller.addListener(_scrollListener);
}
void _scrollListener() {
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
_loadUserPurchases();
}
}
void _loadUserPurchases() async {
if(!_fullLoaded && !_loading) {
_loading = true;
setState(() => loading = true);
String token = await helper.getToken();
Response response = await getUserPurchases(token, widget.userId, _page);
final responseJson = json.decode(response.body);
List purchases = responseJson['results'];
_fullLoaded = _page == responseJson['pages'];
setState(() {
loading = false;
_purchases.addAll(purchases);
});
_page++;
_loading = false;
setState(() => loading = false);
}
}
num extractReturnPoints(Map purchase) {
final returnPoints = purchase['sum_bonus'];
if(returnPoints is num) {
return returnPoints;
} else if(returnPoints is String) {
final RegExp returnPattern = RegExp(r"(\d+)");
final List<Match> digits = returnPattern.allMatches(returnPoints).toList();
return int.parse(digits[0].group(0)) - int.parse(digits[1].group(1));
} else {
return 0;
}
}
void _confirmReturnPurchase(Map purchase, String formattedDate) {
final num totalReturn = extractReturnPoints(purchase);
showDialog(
context: context,
builder: (context) =>
AlertDialog(
title: Text(StringsLocalization.confirmation()),
content: Text(StringsLocalization.returnConfirmation(
purchase['sum_total'], formattedDate, _bonus - totalReturn)),
actions: <Widget>[
FlatButton(
child: Text(StringsLocalization.no()),
onPressed: () => Navigator.pop(context),
),
FlatButton(
child: Text(StringsLocalization.yes()),
onPressed: () {
Navigator.pop(context);
_returnPurchase(purchase, formattedDate, totalReturn);
}
)
],
)
);
}
void _returnPurchase(Map purchase, String formattedDate, num totalReturn) async {
String token = await helper.getToken();
final Response response = await returnPurchase(token, widget.userId, purchase['id']);
if(response.statusCode == 204) {
_bonus += totalReturn;
setState(() {
_purchases.removeWhere((p) => p['id'] == purchase['id']);
});
showDialog(
context: context,
builder: (context) =>
AlertDialog(
title: Text(StringsLocalization.returnConfirmed()),
content: Text(StringsLocalization.returnConfirmedContent(purchase['sum_total'], formattedDate)),
actions: <Widget>[
FlatButton(
child: Text(StringsLocalization.yes()),
onPressed: () => Navigator.pop(context)
)
],
)
);
}
}
}

View File

@@ -125,6 +125,11 @@ class StringsLocalization {
String trimmedVal = val.substring(0, val.length - 3); String trimmedVal = val.substring(0, val.length - 3);
return sprintf(strings['payment_complite'], [val, declineCurrency(int.parse(trimmedVal), code)]); return sprintf(strings['payment_complite'], [val, declineCurrency(int.parse(trimmedVal), code)]);
} }
static String returnConfirmation(String sum, String date, int points) =>
sprintf(strings['return_purchase_content'], [sum, date, points.toString()]);
static String returnConfirmedContent(String sum, String date) =>
sprintf(strings['return_confirmation_content'], [sum, date]);
static String registration() => strings['registration']; static String registration() => strings['registration'];
static String usage() => strings['usage']; static String usage() => strings['usage'];
@@ -218,4 +223,7 @@ class StringsLocalization {
static String joysMinus() => strings['joys_minus']; static String joysMinus() => strings['joys_minus'];
static String joysHint() => strings['joys_hint']; static String joysHint() => strings['joys_hint'];
static String phone() => strings['phone']; static String phone() => strings['phone'];
static String returnLabel() => strings['returnLabel'];
static String returnConfirmed() => strings['return_confirmation'];
static String points() => strings['points'];
} }

View File

@@ -48,6 +48,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.10" version: "0.4.10"
intl:
dependency: "direct main"
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.7"
meta: meta:
dependency: transitive dependency: transitive
description: description:

View File

@@ -9,6 +9,7 @@ dependencies:
image_picker: '^0.4.1' # use for ask permissions @ iOS image_picker: '^0.4.1' # use for ask permissions @ iOS
xml: "^3.0.0" xml: "^3.0.0"
sentry: 2.2.0 sentry: 2.2.0
intl: 0.15.7
flutter: flutter:
sdk: flutter sdk: flutter
@@ -41,6 +42,7 @@ flutter:
- assets/settings_arrow.png - assets/settings_arrow.png
- assets/help.png - assets/help.png
- assets/check.png - assets/check.png
- assets/return.png
- assets/exit.png - assets/exit.png
- assets/activate_token_message_background.png - assets/activate_token_message_background.png
- assets/active_token_message_background.png - assets/active_token_message_background.png