Добавлена проверка статуса токена, обработка статуса токена

This commit is contained in:
Ivan Murashov
2017-07-18 16:49:47 +03:00
parent fc07eca469
commit ff8ddf4334
12 changed files with 287 additions and 60 deletions

View File

@@ -25,7 +25,7 @@ android {
defaultConfig { defaultConfig {
targetSdkVersion 21 targetSdkVersion 21
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
applicationId "com.dinnect.checker" applicationId "com.dinect.checker"
} }
buildTypes { buildTypes {

View File

@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dinnect.checker" package="com.dinect.checker"
android:versionCode="1" android:versionCode="1"
android:versionName="0.0.1"> android:versionName="0.0.1">
@@ -18,7 +18,7 @@
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<application android:name="io.flutter.app.FlutterApplication" android:label="checker" android:icon="@mipmap/ic_launcher"> <application android:name="io.flutter.app.FlutterApplication" android:label="checker" android:icon="@mipmap/ic_launcher">
<activity android:name="com.dinnect.checker.activity.MainActivity" <activity android:name="com.dinect.checker.activity.MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar" android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
@@ -30,11 +30,11 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.dinnect.checker.activity.CameraActivity" <activity android:name="com.dinect.checker.activity.CameraActivity"
android:theme="@android:style/Theme.Black.NoTitleBar"/> android:theme="@android:style/Theme.Black.NoTitleBar"/>
<service <service
android:name="com.dinnect.checker.service.RegistrationIntentService" android:name="com.dinect.checker.service.RegistrationIntentService"
android:exported="false"/> android:exported="false"/>
</application> </application>

View File

@@ -1,4 +1,4 @@
package com.dinnect.checker.activity; package com.dinect.checker.activity;
import android.app.Activity; import android.app.Activity;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
@@ -29,8 +29,8 @@ import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet; import net.sourceforge.zbar.SymbolSet;
import net.sourceforge.zbar.Config; import net.sourceforge.zbar.Config;
import com.dinnect.checker.R; import com.dinect.checker.R;
import com.dinnect.checker.view.CameraPreview; import com.dinect.checker.view.CameraPreview;
public class CameraActivity extends Activity { public class CameraActivity extends Activity {

View File

@@ -1,10 +1,12 @@
package com.dinnect.checker.activity; package com.dinect.checker.activity;
import android.os.Bundle; import android.os.Bundle;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.dinnect.checker.activity.CameraActivity; import android.content.SharedPreferences;
import com.dinnect.checker.service.RegistrationIntentService; import com.dinect.checker.activity.CameraActivity;
import com.dinect.checker.service.RegistrationIntentService;
import io.flutter.app.FlutterActivity; import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugins.GeneratedPluginRegistrant;
@@ -16,32 +18,56 @@ import io.flutter.plugin.common.MethodChannel.Result;
import com.google.android.gms.iid.InstanceID; import com.google.android.gms.iid.InstanceID;
import java.util.Map;
public class MainActivity extends FlutterActivity { public class MainActivity extends FlutterActivity {
private static final String INSTANCE_ID_CHANNEL = "com.dinnect.checker/instance_id"; private static final String INSTANCE_ID_CHANNEL = "com.dinect.checker/instance_id";
private static final String PREF_POS_TOKEN = "pref_pos_token";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this); GeneratedPluginRegistrant.registerWith(this);
final SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
new MethodChannel(getFlutterView(), INSTANCE_ID_CHANNEL).setMethodCallHandler( new MethodChannel(getFlutterView(), INSTANCE_ID_CHANNEL).setMethodCallHandler(
new MethodCallHandler() { new MethodCallHandler() {
@Override @Override
public void onMethodCall(MethodCall call, Result result) { public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getInstanceID")) { switch (call.method) {
InstanceID instanceID = InstanceID.getInstance(MainActivity.this); case "getInstanceID":
String id = instanceID.getId();
if (id != null) { InstanceID instanceID = InstanceID.getInstance(MainActivity.this);
result.success(id); String id = instanceID.getId();
} else {
result.error("UNAVAILABLE", "Can't get instanceID.", null);
}
} else { if (id != null) {
result.notImplemented(); result.success(id);
} else {
result.error("UNAVAILABLE", "Can't get instanceID.", null);
}
break;
case "saveToken":
Map arguments = call.arguments();
String token = (String) arguments.get("token");
Log.d("kifio", token);
preferences.edit().putString(PREF_POS_TOKEN, token).apply();
break;
case "getToken":
result.success(preferences.getString(PREF_POS_TOKEN, null));
break;
case "startScanner":
startActivity(new Intent(MainActivity.this, CameraActivity.class));
break;
default:
result.notImplemented();
break;
} }
} }
}); });
@@ -55,4 +81,12 @@ public class MainActivity extends FlutterActivity {
} }
public void saveToken() {
}
public void getToken() {
}
} }

View File

@@ -1,4 +1,4 @@
package com.dinnect.checker.service; package com.dinect.checker.service;
import android.app.IntentService; import android.app.IntentService;
import android.content.Intent; import android.content.Intent;

View File

@@ -1,4 +1,4 @@
package com.dinnect.checker.view; package com.dinect.checker.view;
import java.io.IOException; import java.io.IOException;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

103
lib/activate_token.dart Normal file
View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'main.dart';
/// Экран регистрации магазина и кассы.
class FinishRegistrationScreen extends StatefulWidget {
@override State createState() => new _RegistrationScreenState();
}
class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
@override Widget build(BuildContext context) {
return new Scaffold(appBar: _getAppBar(), body: _getScreen(context));
}
AppBar _getAppBar() {
return new AppBar(title: new Text("Регистрация магазина"),
backgroundColor: const Color(0xff4272e7), actions: <Widget>[
new IconButton(
icon: new Icon(Icons.help_outline),
tooltip: 'Air it',
onPressed: faq,
),
new IconButton(
icon: new Image(height: 24.0, width: 24.0, image: new AssetImage(logout_png)),
tooltip: 'Restitch it',
onPressed: logout,
)
]);
}
Widget _getScreen(BuildContext context) {
return new Center(child: new Column(children: <Widget>[
_getLogo(),
_getDecoratedInputField(merchantIDHint),
_getDecoratedInputField(posIDHint),
_getMessage(),
_getButton(context)
]));
}
Container _getLogo() {
return new Container(padding: new EdgeInsets.only(top: 16.0, bottom: 16.0),
child: new Image.asset(logo_png, height: 24.0, width: 156.0));
}
Container _getDecoratedInputField(String hint) {
return new Container(
padding: new EdgeInsets.only(left: 28.0, right: 28.0, top: 8.0),
child: new Container(height: 48.0,
padding: new EdgeInsets.only(left: 16.0, right: 16.0),
decoration: _getDecoraionForInputField(),
child: _getInputField(hint))) ;
}
TextField _getInputField(String hint) {
return new TextField(decoration: new InputDecoration(hintText: hint,
hideDivider: true,
hintStyle: new TextStyle(color: const Color(0xffa5a5a5),
fontSize: 16.0)), onChanged: null);
}
Container _getMessage() {
return new Container(padding: new EdgeInsets.only(top: 20.0, left: 26.0, right: 26.0),
child: new Container(height: 128.0, decoration: _getDecoraionForMessageField(),
padding: new EdgeInsets.only(top: 16.0, bottom: 8.0, left: 28.0, right: 28.0),
child: new Text('Запрос на активацию программы отправлен, дождитесь подтверждения активации администратором',
textAlign: TextAlign.center, style: new TextStyle(fontWeight: FontWeight.bold, color: const Color(0xff4e3a19)))));
}
Decoration _getDecoraionForMessageField() {
return new BoxDecoration(image: new DecorationImage(
image: new ExactAssetImage(activate_token_bg_png), fit: BoxFit.fill));
}
Decoration _getDecoraionForInputField() {
return new BoxDecoration(color: Colors.white,
border: new Border.all(
color: const Color(0xffcfd8dc), width: 1.0,),
borderRadius: new BorderRadius.all(new Radius.circular(4.0)));
}
Container _getButton(BuildContext context) {
return new Container(padding: new EdgeInsets.only(top: 36.0),
child: new Container(height: 64.0, padding: new EdgeInsets.all(8.0),
child: new RaisedButton(child: new Text('Обновить статус активации',
style: new TextStyle(color: Colors.white)),
onPressed: _checkToken(context),
disabledColor: const Color(0xffbfbfbf),
color: const Color(0xff3078c0))));
}
}
_checkToken(BuildContext context) {
checkToken(context, new CheckTokenCallback());
}
class CheckTokenCallback extends Callback {
call(BuildContext context) {
startScanner();
}
}

View File

@@ -1,17 +1,75 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'splash.dart'; import 'splash.dart';
import 'dart:async';
import 'dart:convert';
const String _name = 'Ivan Murashov'; /// Главный класс приложения.
/// Здесь распоосложены константы и некоторые методы, которые могут вызываться с разных экранов приложения.
// Serious constants
const String intUrl = 'https://pos-api-int.dinect.com/20130701/'; const String intUrl = 'https://pos-api-int.dinect.com/20130701/';
const String intToken = '9fec83cdca38c357e6b65dbb17514cdd36bf2a08'; 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';
final httpClient = createHttpClient(); final httpClient = createHttpClient();
void main() { void main() {
runApp(new Checker()); runApp(new Checker());
} }
/// Токен кассы. Инициализируется при регистрации.
String token;
/// Проверка статуса токена. Токен может быть активирован, либо не активирован.
void checkToken(BuildContext context, Callback callback) {
String url = intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken;
print(url);
httpClient.get(url).then((response) {
print(response.body);
Map parsedMap = JSON.decode(response.body);
bool active = parsedMap['active'];
if (!active) {
callback.call(context);
} else {
// Запускается экран сканера, токен кассы активирован, с его помощью можно делать запросы к pos-api.
startScanner();
}
}).catchError((error) {
print(error.toString());
});
}
/// Запуск спецефичной для каждой платформы части приложения - сканера.
/// Может производиться с нескольких экранов (splash, finish_registration).
startScanner() async{
const platform = const MethodChannel('com.dinect.checker/instance_id');
await platform.invokeMethod('startScanner');
}
/// Навигация по приложению.
/// widget - следующий экран приложения.
pushRoute(BuildContext context, Widget widget) {
Navigator.of(context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return widget;
}));
}
class Checker extends StatelessWidget { class Checker extends StatelessWidget {
@override Widget build(BuildContext context) { @override Widget build(BuildContext context) {
return new MaterialApp(title: "DemoApp", home: new SplashScreen()); return new MaterialApp(title: "DemoApp", home: new SplashScreen());
@@ -28,3 +86,7 @@ abstract class BaseState<T> extends State<StatefulWidget> {
} }
} }
abstract class Callback {
void call(BuildContext context);
}

View File

@@ -1,6 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'main.dart'; import 'main.dart';
import 'activate_token.dart';
/// Экран регистрации магазина и кассы. /// Экран регистрации магазина и кассы.
class RegistrationScreen extends StatefulWidget { class RegistrationScreen extends StatefulWidget {
@@ -9,14 +11,11 @@ class RegistrationScreen extends StatefulWidget {
class _RegistrationScreenState extends BaseState<RegistrationScreen> { class _RegistrationScreenState extends BaseState<RegistrationScreen> {
static const String _merchantIDHint = 'ID магазина';
static const String _posIDHint = 'Номер кассы';
String _merchantID = ""; String _merchantID = "";
String _posID = ""; String _posID = "";
@override Widget build(BuildContext context) { @override Widget build(BuildContext context) {
return new Scaffold(appBar: _getAppBar(), body: _getScreen()); return new Scaffold(appBar: _getAppBar(), body: _getScreen(context));
} }
AppBar _getAppBar() { AppBar _getAppBar() {
@@ -35,25 +34,19 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
]); ]);
} }
Widget _getScreen() { Widget _getScreen(BuildContext context) {
return new Center(child: new Column(children: _getChildren())); return new Center(child: new Column(children: <Widget>[
_getLogo(),
_getDecoratedInputField(merchantIDHint, 0.0),
_getDecoratedInputField(posIDHint, 36.0),
_getButton(context)
]));
} }
List<Widget> _getChildren() {
return<Widget>[
_getLogo(),
_getDecoratedInputField(_merchantIDHint, 0.0),
_getDecoratedInputField(_posIDHint, 36.0),
_getButton()
];
}
Container _getLogo() { Container _getLogo() {
return new Container(height: 192.0, return new Container(height: 192.0,
child: new Center( child: new Image.asset(logo_png, height: 24.0, width: 156.0));
child: new Image.asset('assets/registration_logo.png',
height: 24.0,
width: 156.0)));
} }
Container _getDecoratedInputField(String hint, double topPadding) { Container _getDecoratedInputField(String hint, double topPadding) {
@@ -75,9 +68,9 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
void _handleUserInput(String hint, String text) { void _handleUserInput(String hint, String text) {
if (text.length > 0) { if (text.length > 0) {
setState(() { setState(() {
if (hint == _merchantIDHint) if (hint == merchantIDHint)
_merchantID = text; _merchantID = text;
else if (hint == _posIDHint) else if (hint == posIDHint)
_posID = text; _posID = text;
}); });
} }
@@ -90,13 +83,14 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
borderRadius: new BorderRadius.all(new Radius.circular(4.0))); borderRadius: new BorderRadius.all(new Radius.circular(4.0)));
} }
Container _getButton() { Container _getButton(BuildContext context) {
return new Container(padding: new EdgeInsets.only(top: 36.0), return new Container(padding: new EdgeInsets.only(top: 36.0),
child: new Container(height: 64.0, padding: new EdgeInsets.all(8.0), child: new Container(height: 64.0, padding: new EdgeInsets.all(8.0),
child: new RaisedButton(child: new Text('ЗАРЕГИСТРИРОВАТЬ', child: new RaisedButton(child: new Text('ЗАРЕГИСТРИРОВАТЬ',
style: new TextStyle(color: Colors.white)), style: new TextStyle(color: Colors.white)),
onPressed: _isFieldsAreFilled() ? () => _register(_merchantID) : null, onPressed: _isFieldsAreFilled() ? () => _registerShop(context, _merchantID) : null,
disabledColor: const Color(0xffbfbfbf)))); disabledColor: const Color(0xffbfbfbf),
color: const Color(0xff3078c0))));
} }
_isFieldsAreFilled() { _isFieldsAreFilled() {
@@ -105,9 +99,13 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
return _merchantID.length == 5 && _posID.length > 0; return _merchantID.length == 5 && _posID.length > 0;
} }
_register(String merchantShop) async { void _registerShop(BuildContext context, String merchantShop) {
_register(context, merchantShop);
}
const platform = const MethodChannel('com.dinnect.checker/instance_id'); _register(BuildContext context, String merchantShop) async {
const platform = const MethodChannel('com.dinect.checker/instance_id');
String url = intUrl + 'tokens/?_dmapptoken=' + intToken; String url = intUrl + 'tokens/?_dmapptoken=' + intToken;
String pos = await platform.invokeMethod('getInstanceID'); String pos = await platform.invokeMethod('getInstanceID');
print(pos); print(pos);
@@ -127,6 +125,12 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
httpClient.post(url, body: body).then((response) { httpClient.post(url, body: body).then((response) {
print(response.body); print(response.body);
Map parsedMap = JSON.decode(response.body);
token = parsedMap['token'];
platform.invokeMethod('saveToken', {'token' : token}).then((value) {
print(value.toString());
});
pushRoute(context, new FinishRegistrationScreen());
}).catchError((error) { }).catchError((error) {
print(error.toString()); print(error.toString());
}); });

View File

@@ -1,24 +1,47 @@
import 'dart:async'; import 'package:flutter/services.dart';
import 'registration.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'main.dart';
import 'registration.dart';
import 'activate_token.dart';
import 'dart:async';
class SplashScreen extends StatelessWidget { class SplashScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Splash скрин зависает мимнимум на 1 секунду.
// После этого начинается проверка токена.
new Future.delayed(const Duration(milliseconds: 1000), () { new Future.delayed(const Duration(milliseconds: 1000), () {
_goToNextScreen(context); _showNextScreen(context);
}); });
return new Image.asset('assets/splash.png', fit: BoxFit.cover); return new Image.asset(logo_png, fit: BoxFit.cover);
} }
_goToNextScreen(BuildContext context) async { /// Запуск следующего экрана приложения.
Navigator.of(context).push(new MaterialPageRoute<Null>( _showNextScreen(BuildContext context) async {
builder: (BuildContext context) {
return new RegistrationScreen(); const platform = const MethodChannel('com.dinect.checker/instance_id');
})); token = await platform.invokeMethod('getToken');
// В случае, если в приложении отсутствует токен,
// необходимо запустить регистрацию кассы.
if (token == null) {
pushRoute(context, new RegistrationScreen());
} else {
checkToken(context, new CheckTokenCallback());
}
}
}
class CheckTokenCallback extends Callback {
/// Запускается экран ожидания активации токена.
/// В реальности токен активируется в админке вручную,
/// на тестовом сервере токен активируется через несколько минут после создания.
call(BuildContext context) {
pushRoute(context, new FinishRegistrationScreen());
} }
} }

View File

@@ -23,6 +23,7 @@ flutter:
- assets/registration_logo.png - assets/registration_logo.png
- assets/splash.png - assets/splash.png
- assets/logout.png - assets/logout.png
- assets/activate_token_message_background.png
# To add assets from package dependencies, first ensure the asset # To add assets from package dependencies, first ensure the asset
# is in the lib/ directory of the dependency. Then, # is in the lib/ directory of the dependency. Then,