Вынес работу с сетью и общие методы в отдельные файлы

This commit is contained in:
kifio
2017-07-27 08:45:47 +03:00
parent 73e139f925
commit d0b5b17334
11 changed files with 256 additions and 206 deletions

View File

@@ -47,6 +47,10 @@ import android.view.SurfaceView;
import java.io.IOException; import java.io.IOException;
import java.lang.Exception; import java.lang.Exception;
import java.util.List; import java.util.List;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import com.dinect.checker.R; import com.dinect.checker.R;
public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback { public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback {
@@ -57,7 +61,7 @@ public class CameraActivity extends AppCompatActivity implements SurfaceHolder.C
private HashMap<String, Integer> mContours = new HashMap<>(); private HashMap<String, Integer> mContours = new HashMap<>();
private boolean mBarcodeScanned = false; private boolean mBarcodeScanned = false;
private boolean previewing = true; private boolean previewing = false;
private Handler autoFocusHandler; private Handler autoFocusHandler;
private int mOffset; private int mOffset;
@@ -117,12 +121,14 @@ public class CameraActivity extends AppCompatActivity implements SurfaceHolder.C
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
mCamera.startPreview(); mCamera.startPreview();
previewing = true;
} }
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
mCamera.stopPreview(); mCamera.stopPreview();
previewing = false;
} }
@Override @Override
@@ -138,6 +144,7 @@ public class CameraActivity extends AppCompatActivity implements SurfaceHolder.C
try { try {
mCamera.setPreviewDisplay(holder); mCamera.setPreviewDisplay(holder);
mCamera.startPreview(); mCamera.startPreview();
previewing = true;
try { try {
mCamera.autoFocus(autoFocusCallback); mCamera.autoFocus(autoFocusCallback);
} catch(Exception e) { } catch(Exception e) {
@@ -207,8 +214,15 @@ public class CameraActivity extends AppCompatActivity implements SurfaceHolder.C
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
Intent intent = new Intent();
if (item.getItemId() == R.id.logout) { if (item.getItemId() == R.id.logout) {
setResult(RESULT_OK); intent.putExtra("item", "logout");
setResult(RESULT_OK, intent);
finish();
return true;
} else if (item.getItemId() == R.id.faq) {
intent.putExtra("item", "faq");
setResult(RESULT_OK, intent);
finish(); finish();
return true; return true;
} }
@@ -253,8 +267,9 @@ public class CameraActivity extends AppCompatActivity implements SurfaceHolder.C
private Runnable doAutoFocus = new Runnable() { private Runnable doAutoFocus = new Runnable() {
public void run() { public void run() {
if (previewing) if (previewing) {
mCamera.autoFocus(autoFocusCallback); mCamera.autoFocus(autoFocusCallback);
}
} }
}; };

View File

@@ -108,19 +108,25 @@ public class MainActivity extends FlutterActivity {
if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_CANCELED) { if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_CANCELED) {
finish(); finish();
} else if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_OK) { } else if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_OK) {
if (data == null) { if (data != null) {
mChannel.invokeMethod("foo", null);
} else {
String code = data.getExtras().getString("code", null); String code = data.getExtras().getString("code", null);
if (code == null) { if (code != null) {
mChannel.invokeMethod("foo", null); mChannel.invokeMethod("purchase", code);
} else { } else {
mChannel.invokeMethod("purchase", code); String menuItem = data.getExtras().getString("item", null);
Log.d("item", menuItem);
if (menuItem != null) {
mChannel.invokeMethod(menuItem, null);
} }
}
} }
} }
} }
private void handleItemClick() {
}
private void getDocID() { private void getDocID() {
} }

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'main.dart'; import 'common.dart';
import 'consts.dart';
import 'network.dart';
import 'dart:convert'; // Пакет для обработки json с ответом от сервера. import 'dart:convert'; // Пакет для обработки json с ответом от сервера.
import 'base_state.dart'; import 'base_state.dart';
@@ -40,7 +42,7 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
handleTap() { handleTap() {
if (_tokenActive) { if (_tokenActive) {
startScanner(context); startScanner(context);
} else { } else {
checkTokenStatus(context).then((response) { checkTokenStatus(context).then((response) {

View File

@@ -1,16 +1,15 @@
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:async';
import 'main.dart'; import 'common.dart';
import 'faq.dart'; import 'network.dart';
import 'consts.dart';
abstract class BaseState<T> extends State<T> { abstract class BaseState<T> extends State<T> {
bool loading = false; bool loading = false;
String error = null; String error = null;
String textFieldValue = ""; String textFieldValue = '';
TextEditingController controller = new TextEditingController(); TextEditingController controller = new TextEditingController();
@@ -28,7 +27,7 @@ abstract class BaseState<T> extends State<T> {
} }
getFaqButton() { getFaqButton() {
return new IconButton(icon: new Icon(Icons.help_outline), onPressed: () => faq()); return new IconButton(icon: new Icon(Icons.help_outline), onPressed: () => faq(context));
} }
getLogoutButton() { getLogoutButton() {
@@ -45,11 +44,6 @@ abstract class BaseState<T> extends State<T> {
String getHint(); String getHint();
faq() {
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => new FAQScreen());
Navigator.of(context).push(route);
}
/// Метод возвращает контейнер с отступами, который содержит картинку с логотипом. /// Метод возвращает контейнер с отступами, который содержит картинку с логотипом.
getLogo() { getLogo() {
double containerHeight = 92.0; double containerHeight = 92.0;

111
lib/common.dart Normal file
View File

@@ -0,0 +1,111 @@
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'network.dart';
import 'purchase.dart';
import 'faq.dart';
// Канал для взаимодействия с кодом платформы.
const platform = const MethodChannel('com.dinect.checker/instance_id');
/// Токен кассы. Инициализируется при регистрации.
String token;
// Метод обеспечивает замену текущего объекта route новым.
pushRoute(BuildContext context, Widget widget) {
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => widget);
Navigator.of(context).pushReplacement(route);
}
// Добавление route, с возможностью вернуться к предыдущему экрану.
faq(BuildContext context) {
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => new FAQScreen());
Navigator.of(context).push(route);
}
// В методе отправляется запрос на удаление токена кассы, очищаются SharedPreferences приложения.
logout(BuildContext context) {
VoidCallback positiveCalback = () {
if (token != null) {
deleteToken(token).then((response) async {
print(response.body);
await platform.invokeMethod('removeKeys');
pushRoute(context, new RegistrationScreen()); // Запускаем регистрацию
}).catchError((error) {
print(error.toString());
});
} else {
Navigator.of(context).pop();
Navigator.of(context).pop();
}
};
showYesNoDialog(context, 'Подтверждение', 'Вы действительно хотите выйти и ввести другой номер магазина?', positiveCalback);
}
/// Запуск спецефичной для каждой платформы части приложения - сканера.
/// Может производиться с нескольких экранов (splash, finish_registration).
startScanner(BuildContext context) async {
// Канал ловит вызовы методов из "нативной" части приложения.
// Могут быть вызваны либо logaut либо faq, либо purchase.
if (token != null) {
platform.setMethodCallHandler((MethodCall call) async {
print('call.method: ${call.method}');
if (call.method == 'logout') {
logout(context);
} else if (call.method == 'faq') {
faq(context);
} else {
List usersList = JSON.decode(call.arguments);
print('usersList.length: ${usersList.length}');
if (usersList.length > 0) {
pushRoute(context, new PurchaseScreen(usersList[0], card));
}
// var card = ;
// String url = 'http://pos-api-int.dinect.com/20130701/users/?auto=${card}';
// print('url: ' + url);
// var headers = {
// 'DM-Authorization': 'dmapptoken 9fec83cdca38c357e6b65dbb17514cdd36bf2a08',
// 'Authorization': 'dmtoken ${token}'
// };
// httpClient.get(url, headers: headers).then((response) {
// print(response.body);
// }).catchError((error) {
// print(error.toString());
// });
}
});
}
await platform.invokeMethod('startScanner');
}
// Запуск диалога с двумя кнопками
showYesNoDialog(BuildContext context, String title, String content, VoidCallback positiveCallback) {
showDialog(context: context, child: new AlertDialog(
title: new Text(title),
content: new Text(content),
actions: <Widget>[
new FlatButton(
child: new Text('Нет'),
onPressed: () {
Navigator.of(context).pop();
}
),
new FlatButton(
child: new Text('Да'),
onPressed: positiveCallback)]));
}

35
lib/consts.dart Normal file
View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
// Serious constants
const String intUrl = 'https://pos-api-int.dinect.com/20130701/';
const String intToken = '9fec83cdca38c357e6b65dbb17514cdd36bf2a08';
// Hints
const String merchantIDHint = 'ID магазина';
const String posIDHint = 'Номер кассы';
// Assets
const String logo_png = 'assets/registration_logo.png';
const String splash_png = 'assets/splash.png';
const String logout_png = 'assets/logout.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 expansion_icon_png = 'assets/expansion_icon.png';
const String powered_by_dinect_splash_png = 'assets/powered_by_dinect_splash.png';
const String powered_by_dinect_png = 'assets/powered_by_dinect.png';
// Colors
const Color primaryColor = const Color(0xffeb0004);
const Color greyTextColor = const Color(0xffa5a5a5);
const Color textBorderColor = const Color(0xffcfd8dc);
const Color tokenActiveTextColor = const Color(0xff1f5a1f);
const Color tokenActivateTextColor = const Color(0xff4e3a19);
const Color greenBackground = const Color(0xff8ae28a);
const Color faqGrey = const Color(0xff5b5b5b);
const Color faqTitlesColor = const Color(0xff404040);
// Dimens
const double verticalMargin = 28.0;
const double buttonVerticalMargin = 42.0;
const double buttonHeight = 48.0;
const double iconHeight = 20.0;

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'main.dart';
import 'base_state.dart'; import 'base_state.dart';
import 'consts.dart';
/// Класс содержит заголовки и текст блоков FAQ. /// Класс содержит заголовки и текст блоков FAQ.
class Entry { class Entry {

View File

@@ -1,169 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'splash.dart'; import 'splash.dart';
import 'registration.dart'; import 'consts.dart';
import 'dart:async';
import 'dart:convert';
import 'purchase.dart';
/// Главный класс приложения.
/// Здесь распоосложены константы и некоторые методы, которые могут вызываться с разных экранов приложения.
// Serious constants
const String intUrl = 'https://pos-api-int.dinect.com/20130701/';
const String intToken = '9fec83cdca38c357e6b65dbb17514cdd36bf2a08';
// Hints
const String merchantIDHint = 'ID магазина';
const String posIDHint = 'Номер кассы';
// Assets
const String logo_png = 'assets/registration_logo.png';
const String splash_png = 'assets/splash.png';
const String logout_png = 'assets/logout.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 expansion_icon_png = 'assets/expansion_icon.png';
const String powered_by_dinect_splash_png = 'assets/powered_by_dinect_splash.png';
const String powered_by_dinect_png = 'assets/powered_by_dinect.png';
// Colors
const Color primaryColor = const Color(0xffeb0004);
const Color greyTextColor = const Color(0xffa5a5a5);
const Color textBorderColor = const Color(0xffcfd8dc);
const Color tokenActiveTextColor = const Color(0xff1f5a1f);
const Color tokenActivateTextColor = const Color(0xff4e3a19);
const Color greenBackground = const Color(0xff8ae28a);
const Color faqGrey = const Color(0xff5b5b5b);
const Color faqTitlesColor = const Color(0xff404040);
// Dimens
const double verticalMargin = 28.0;
const double buttonVerticalMargin = 42.0;
const double buttonHeight = 48.0;
const double iconHeight = 20.0;
const platform = const MethodChannel('com.dinect.checker/instance_id');
// HttpClient
final httpClient = createHttpClient();
/// Токен кассы. Инициализируется при регистрации.
String token;
/// Точка входа в приложение. /// Точка входа в приложение.
void main() { void main() {
runApp(new Checker()); runApp(new Checker());
} }
/// Проверка статуса токена. Токен может быть активирован, либо не активирован.
checkTokenStatus(BuildContext context) async {
return httpClient.get(intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken);
}
createToken(String merchantId) async {
String url = intUrl + 'tokens/?_dmapptoken=' + intToken;
String posID = await platform.invokeMethod('getPosID');
print('posID: ${posID}');
print('merchantId: ${merchantId}');
// Поле description - необязательное.
var body = {
'merchant_shop': merchantId,
'pos': posID,
};
return httpClient.post(url, body: body);
}
/// Запуск спецефичной для каждой платформы части приложения - сканера.
/// Может производиться с нескольких экранов (splash, finish_registration).
startScanner(BuildContext context) async {
// Канал ловит вызовы методов из "нативной" части приложения.
// Могут быть вызваны либо logaut либо faq, либо purchase.
if (token != null) {
platform.setMethodCallHandler((MethodCall call) async {
if (call.method == 'foo') {
logout(context);
} else {
var card = call.arguments;
print('card: ' + card);
String url = 'http://pos-api-int.dinect.com/20130701/users/?auto=${card}';
print('url: ' + url);
var headers = {
'DM-Authorization': 'dmapptoken 9fec83cdca38c357e6b65dbb17514cdd36bf2a08',
'Authorization': 'dmtoken ${token}'
};
httpClient.get(url, headers: headers).then((response) {
print(response.body);
List usersList = JSON.decode(response.body);
print('usersList.length: ${usersList.length}');
if (usersList.length > 0) {
pushRoute(context, new PurchaseScreen(usersList[0], card));
}
}).catchError((error) {
print(error.toString());
});
}
});
}
await platform.invokeMethod('startScanner');
}
logout(BuildContext context) {
showDialog(context: context, child: new AlertDialog(
title: new Text('Подтверждение'),
content: new Text('Вы действительно хотите выйти и ввести другой номер магазина?'),
actions: <Widget>[
new FlatButton(
child: new Text('Нет'),
onPressed: () {
Navigator.of(context).pop();
}
),
new FlatButton(
child: new Text('Да'),
onPressed: () {
if (token != null) {
String url = intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken;
print(url);
httpClient.delete(url).then((response) async {
print(response.body);
await platform.invokeMethod('removeKeys');
Navigator.of(context).pop();
Navigator.of(context).pop();
pushRoute(context, new RegistrationScreen());
}).catchError((error) {
print(error.toString());
});
} else {
Navigator.of(context).pop();
Navigator.of(context).pop();
}
}
)]));
}
/// Навигация по приложению.
/// widget - следующий экран приложения.
pushRoute(BuildContext context, Widget widget) {
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => widget);
Navigator.of(context).pushReplacement(route);
}
class Checker extends StatelessWidget { class Checker extends StatelessWidget {
@override Widget build(BuildContext context) { @override Widget build(BuildContext context) {
return new MaterialApp(title: "AutoClub", return new MaterialApp(title: "AutoClub",
@@ -174,5 +17,3 @@ class Checker extends StatelessWidget {
)); ));
} }
} }

39
lib/network.dart Normal file
View File

@@ -0,0 +1,39 @@
import 'package:flutter/services.dart';
import 'dart:async';
import 'consts.dart';
// Клиент http приложения
final httpClient = createHttpClient();
// Попытка создать токен для кассы.
// В случае если токен для кассы уже существует, вернется ошибка 409.
// На сервере есть ограничение в 40 токенов.
createToken(String merchantId, String posID) async {
String url = intUrl + 'tokens/?_dmapptoken=' + intToken;
// Поле description - необязательное.
var body = {
'merchant_shop': merchantId,
'pos': posID,
};
return httpClient.post(url, body: body);
}
// Проверка статуса токена. В ответе приходит параметр active, который может быть либо true, либо false,.
checkTokenStatus(String token) async {
return httpClient.get(intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken);
}
// Удаление токена на сервере.
deleteToken(String token) async {
String url = intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken;
print(url);
return httpClient.delete(url).then();
}
// Удалить токены
// 57e0d09aa935252d2a3463ebc1d61501608a6af9
// f1355ea87375c173695b57afa72f78fedbe5b6c3

View File

@@ -1,14 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:convert'; // Пакет для обработки json с ответом от сервера. import 'dart:convert'; // Пакет для обработки json с ответом от сервера.
import 'dart:async';
import 'main.dart'; import 'common.dart';
import 'network.dart';
import 'consts.dart';
import 'activate_token.dart'; import 'activate_token.dart';
import 'base_state.dart'; import 'base_state.dart';
/// На фото мой сын, большой любитель голых констант.
/// Экран регистрации магазина и кассы. /// Экран регистрации магазина и кассы.
class RegistrationScreen extends StatefulWidget { class RegistrationScreen extends StatefulWidget {
@override State createState() => new _RegistrationScreenState(); @override State createState() => new _RegistrationScreenState();
@@ -54,7 +52,7 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
/// Получение от платформы id установки, формирование запроса на получение токена, сохранение токена. /// Получение от платформы id установки, формирование запроса на получение токена, сохранение токена.
_register(BuildContext context) async { _register(BuildContext context) async {
createToken(textFieldValue).then((response) { createToken(textFieldValue, await platform.invokeMethod('getPosID')).then((response) {
setState(() { setState(() {
error = null; error = null;

View File

@@ -1,12 +1,12 @@
import 'package:flutter/services.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'main.dart'; import 'common.dart';
import 'network.dart';
import 'consts.dart';
import 'registration.dart'; import 'registration.dart';
import 'activate_token.dart'; import 'activate_token.dart';
import 'purchase.dart';
class SplashScreen extends StatelessWidget { class SplashScreen extends StatelessWidget {
@@ -20,14 +20,18 @@ class SplashScreen extends StatelessWidget {
// pushRoute(context, new PurchaseScreen(null)); // pushRoute(context, new PurchaseScreen(null));
}); });
return new Stack(children: <Widget>[new Container(padding: new EdgeInsets.only(left: 48.0, right: 48.0), decoration: getSplashBg()), return new Stack(children: <Widget>[getBackgroundContainer(),
new Align(alignment: FractionalOffset.bottomRight, child: new Align(alignment: FractionalOffset.bottomRight, child:
new Container(margin: new EdgeInsets.only(right: 11.0, bottom: 5.0), child: new Image.asset(powered_by_dinect_splash_png, height: 16.0, width: 122.0)))]); new Container(margin: new EdgeInsets.only(right: 11.0, bottom: 5.0), child: new Image.asset(powered_by_dinect_splash_png, height: 16.0, width: 122.0)))]);
} }
Decoration getSplashBg() { getBackgroundContainer() {
return new BoxDecoration(image: new DecorationImage( const margin = 48.0;
image: new ExactAssetImage(splash_png), fit: BoxFit.cover)); return new Container(padding: new EdgeInsets.only(left: margin, right: margin), decoration: getSplashBackground());
}
getSplashBackground() {
return new BoxDecoration(image: new DecorationImage(image: new ExactAssetImage(splash_png), fit: BoxFit.cover));
} }
/// Запуск следующего экрана приложения. /// Запуск следующего экрана приложения.
@@ -42,20 +46,18 @@ class SplashScreen extends StatelessWidget {
pushRoute(context, new RegistrationScreen()); pushRoute(context, new RegistrationScreen());
} else { } else {
checkTokenStatus(context).then((statusResponse) { checkTokenStatus(token).then((statusResponse) {
handleStatusResponse(context, statusResponse); handleStatusResponse(context, statusResponse);
}).catchError((error) { }).catchError((error) {
print('Handle exception!');
print(error.toString()); print(error.toString());
return false; return false;
}); });
} }
} }
startRegistration() async { // Обработка ответа.
// В случае, если токен был удален может прийти active: false, либо 404.
} // Если токен не активен, попробовать создать его еще раз. В случае успешного создания токена удалить его и перейти на экран регистрации
handleStatusResponse(BuildContext context, var statusResponse) async { handleStatusResponse(BuildContext context, var statusResponse) async {
int code = statusResponse.statusCode; int code = statusResponse.statusCode;
print('resp: ${code}'); print('resp: ${code}');
@@ -73,13 +75,20 @@ class SplashScreen extends StatelessWidget {
if (active) { if (active) {
startScanner(context); startScanner(context);
} else { } else {
createToken(await platform.invokeMethod('getMerchantID')).then((response) { createToken(await platform.invokeMethod('getMerchantID'), await platform.invokeMethod('getPosID')).then((response) {
print('response.body: ${response.body}'); print('response.body: ${response.body}');
if (response.statusCode == 409) { if (response.statusCode == 409) {
pushRoute(context, new FinishRegistrationScreen()); pushRoute(context, new FinishRegistrationScreen());
} else { } else {
startRegistration(); deleteToken(token).then((response) async {
print(response.body);
await platform.invokeMethod('removeKeys');
Navigator.of(context).pop(); // Убираем текущий route
pushRoute(context, new RegistrationScreen()); // Запускаем регистрацию
}).catchError((error) {
print(error.toString());
});
} }
}).catchError((error) { }).catchError((error) {