From 8cf5d4b0288bb1b42bb3bc57fa3d1a552df1cc8b Mon Sep 17 00:00:00 2001 From: Ivan Murashov Date: Fri, 21 Jul 2017 22:35:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F,=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B0=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0,=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BE=D0=BA,=20=D1=81=D0=BA=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checker/activity/CameraActivity.java | 13 +- .../dinect/checker/activity/MainActivity.java | 50 +++- android/app/src/main/res/layout/a_scanner.xml | 33 +++ assets/active_token_message_background.png | Bin 0 -> 10351 bytes lib/activate_token.dart | 109 ++++++--- lib/main.dart | 77 ++++--- lib/purchase.dart | 101 +++++++++ lib/registration.dart | 213 +++++++++++------- lib/splash.dart | 42 ++-- pubspec.yaml | 1 + 10 files changed, 454 insertions(+), 185 deletions(-) create mode 100644 assets/active_token_message_background.png create mode 100644 lib/purchase.dart diff --git a/android/app/src/main/java/com/dinect/checker/activity/CameraActivity.java b/android/app/src/main/java/com/dinect/checker/activity/CameraActivity.java index a39621c..12215a3 100644 --- a/android/app/src/main/java/com/dinect/checker/activity/CameraActivity.java +++ b/android/app/src/main/java/com/dinect/checker/activity/CameraActivity.java @@ -2,6 +2,7 @@ package com.dinect.checker.activity; import android.support.v7.app.AppCompatActivity; import android.content.pm.ActivityInfo; +import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.Log; @@ -69,7 +70,7 @@ public class CameraActivity extends AppCompatActivity { if (actionBar != null) { actionBar.setTitle(getString(R.string.scanner_title)); - actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); } mCamera = getCameraInstance(); @@ -117,6 +118,12 @@ public class CameraActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } + @Override + public void onBackPressed() { + setResult(RESULT_CANCELED); + finish(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -193,6 +200,10 @@ public class CameraActivity extends AppCompatActivity { SymbolSet syms = mScanner.getResults(); for (Symbol sym : syms) { mBarcodeScanned = true; + Intent intent = new Intent(); + intent.putExtra("code", sym.getData()); + setResult(RESULT_OK, intent); + finish(); Toast.makeText(CameraActivity.this, sym.getData(), Toast.LENGTH_SHORT).show(); } } diff --git a/android/app/src/main/java/com/dinect/checker/activity/MainActivity.java b/android/app/src/main/java/com/dinect/checker/activity/MainActivity.java index 04c410e..aee24b7 100644 --- a/android/app/src/main/java/com/dinect/checker/activity/MainActivity.java +++ b/android/app/src/main/java/com/dinect/checker/activity/MainActivity.java @@ -19,8 +19,11 @@ import java.util.Map; public class MainActivity extends FlutterActivity { private static final int START_SCANNER_REQUEST_CODE = 2017; + private static final String PREF_POS_TOKEN = "pref_pos_token"; + private static final String PREF_POS_MERCHANT_ID = "pref_pos_merchant_id"; private MethodChannel mChannel; + private SharedPreferences mPreferences; @Override protected void onCreate(Bundle savedInstanceState) { @@ -28,8 +31,7 @@ public class MainActivity extends FlutterActivity { GeneratedPluginRegistrant.registerWith(this); final String INSTANCE_ID_CHANNEL = "com.dinect.checker/instance_id"; - final String PREF_POS_TOKEN = "pref_pos_token"; - final SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE); + mPreferences = getPreferences(Context.MODE_PRIVATE); mChannel = new MethodChannel(getFlutterView(), INSTANCE_ID_CHANNEL); mChannel.setMethodCallHandler( @@ -47,13 +49,18 @@ public class MainActivity extends FlutterActivity { } 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(); + Map tokenArguments = call.arguments(); + mPreferences.edit().putString(PREF_POS_TOKEN, (String) tokenArguments.get("token")).apply(); break; case "getToken": - result.success(preferences.getString(PREF_POS_TOKEN, null)); + result.success(mPreferences.getString(PREF_POS_TOKEN, null)); + break; + case "saveMerchantID": + Map merchantIDArguments = call.arguments(); + mPreferences.edit().putString(PREF_POS_MERCHANT_ID, (String) merchantIDArguments.get("merchantID")).apply(); + break; + case "getMerchantID": + result.success(mPreferences.getString(PREF_POS_MERCHANT_ID, null)); break; case "startScanner": Intent cameraIntent = new Intent(MainActivity.this, CameraActivity.class); @@ -69,11 +76,28 @@ public class MainActivity extends FlutterActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_OK) { - mChannel.invokeMethod("foo", null); + if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_CANCELED) { + finish(); + } else if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_OK) { + if (data == null) { + logout(); + } else { + String code = data.getExtras().getString("code", null); + if (code == null) { + logout(); + } else { + mChannel.invokeMethod("purchase", code); + } + } } } + private void logout() { + mChannel.invokeMethod("foo", null); + mPreferences.edit().remove(PREF_POS_TOKEN).apply(); + mPreferences.edit().remove(PREF_POS_MERCHANT_ID).apply(); + } + public void startScanner() { } @@ -90,4 +114,12 @@ public class MainActivity extends FlutterActivity { } + public void saveMerchantID() { + + } + + public void getMerchantID() { + + } + } diff --git a/android/app/src/main/res/layout/a_scanner.xml b/android/app/src/main/res/layout/a_scanner.xml index c3a0be0..4fd7ecc 100644 --- a/android/app/src/main/res/layout/a_scanner.xml +++ b/android/app/src/main/res/layout/a_scanner.xml @@ -10,6 +10,39 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> + + + + + + + + + + + + + + + + fH$0*C0HJ8R^Fo#}V2w=oH1{^>6=oS{=+0&|d5uw5`^V&+brF{cFG^gcN;)+8_6N2j zkDa@63iiUGhtP`BrGFuWvcDSW^2{^E*30Y0tcrI_g?np~0oK;LMSaX$V@&P&M*o>s zJ+mcBRng#?jfCjvXzwT5Yg5LTD>5XalviGzvgLv!Lm+fe1{n1L>4$R##w)V3wmn^4 z=iKYC?$iDgk$2|`A5@9X@R@v^msHx$&5Byhm@#wRedYJN_-LeAfZLq7e{khwLHQSa zdEo60<@+ywox9AZnb`dC_@n=)ge(Ize!r+!FT}0xc3PQY>qpn{dbGtDVh@3dYuiN9+E#>VQ}Q*rOWPH$(T_#t>_h zbMW9#7P$)F9$gTnF|azDpfEwo_gHdOx}dal^*%-eJ$p)YZhCg}wy{xg*-e{ap8qNA z@;0e!w9CrV*f|s7yD_w4cR$<5&LmEOox3)~*6^UaNAv4kJlloJ#(qKNTkQyzwy7aCfobqh1aYd0AGJ^HymBH?8x{55%*Q zHeMEUKj~#9DQxh?S`4TV#A(q3VH^#oTunYvx<7Vo<@oyA!uRiBlw0GO z^Q*K$JD7?@>*xzUGK`?~REAUNX0I9^p%IU4gwi2Y%r51zm*CvlC(r9}baXtRHGv_I zg&~3>dR9fEk0}>@NfYH3@=A<$0r;&Xqzght0?w~}*=5dU=d<}97(#FdaqXkyeESC< zmUozF-C^qUU5>;Crzhcsi`d^CqfrHdE3EFst8EXqL4!T9(K|O^~L1kdC_heTCYNIrnuL5 zVZjg~buc)W%@kw65Dr=^saa7fLWHx%pHd$#Rj^rStr^RKF3bg@j_qG_((UQ)arR zh_9QU(*_UCcoVfy{3AJGEWDrMN0FkB8 zKz7o_iq4)mii`3;?X-y`$BSb?_zrv>6S_MYI!9r=-_xa`EfaNZQ=OzK*l^?-Gwm`k zkE1|=sdy_9HBqY9>c_VHqSTnVV#Hi$_69l{wI_1yKuL+XB|~c{1owkwA41eXGp%yL z&b9RLzzxNtw61+8eM?dFVdS%bMvtqmr3Yer2ac`NVx~F)*2F^kn&#V;B7R48BZ-pA zIU{H?s=JUdZT)qRy(rvWe7S@K8;ON{&SX!n6db{6L{QSJwn=dmz8SGkwB;Y}jys3& zZ8scsM5S5L6$9A!dQ)m)SE=xr7L+(X7!anZL2`0sY| ze|tBT`-U$sA}$&k8+jj#`?8l+>@!c5YRD{ADxFP^w^3C5LC`c%N^$WI!w6)RW%VyP zy6;f?e-Ubj`#nBk4|l01usy07`z!LVDupKeX2p!=38kni(^W;K;5}pp#`{fj+hZxA z-HP+1Xo}C|=N}fX#N}=zYcnllMOrsAVtuynoY5Z)T>l8ya=Xks(o&OD^(K2#-LbMI zgO;q(K^1>@^j#vn+VX3}&z@Cb<>RX{8_Q=%!}x%q*F1^L!Q0=fOpHC-VrY9}fW;Rv zG`ubMmB zk_ZKkAMEbG@X6}G?AN~W66nju)s(? z(%Q~@>>i>j$6+&n6*&A^y-~mmp5@@mfqo)E{A1jQ(pgd={`o3r*d)Na@x|5inK4yA zzB@CgH2HC}MO7_rru(<_1&&&1C8hXML5Or^2)99$DJNb=P%B-*tLkpM)*$QQ>e{*| z(;41R=P&~s-wV&gsCN-R@}8siFj~A^v3DqlSx0xEQLkQSot~Zzczq&!BjU)V10%zL zR-7ER8lz{aQ!DtnVtFDc^EX@l8;dz-C%0Ndp@3R)`*4v}>p#{L(n5x<-hzY6?6I@_Jk@V3t8_QBOe>wTH(caqj#rfdr{WZw93~Oi^~W zM)1ClFs+~wWFm2Yh7nj zq_T%@J#1i=OqY^TTO6^QRSBkSvi;3(uMKv0v9QmZ8Vf$vZF4B>Qpxm0P=@&1tPDwK zt*{%^kgng%j3-Y{vb|9!O-_bW4TL@4UYRHrem_!e;uiOQb7+KFzkK^M*)7MSf*K8c zMF?K4FnlqYt1d{{4a1e^ufBn?1)5yhk`$S=;hGp9%KzR?@k|+?ROuYRT!M~Pnx7Qy0{N49gOYx%&Ihl7_IdL zhP)i8YUvJN8B{M%2kHj-A}$AEJFq1yK}`voE2^@8nX zjmqM(W$VGoE1XpS@4E_@2*4%<*2fKMSLIu-5}StqzEOCwna$Kzw>YFD6N6R=Q6*D_2d9 zw!V_ID@(=(O^kRa1@x0-WypL1TNURvta82x z=FF4*raq(pod!4i)2`*M$ah7_ORXD3JS5QB zMIKR<=d4qYS$Z;M_Q7{x5G|{ZahAXTS{iKlO-1Eao4N=3H7s~*v?Ra#d%ROoEwSz+ z2tDR|LEV- z6lyN_nA3(d`Tk9AzsO}RTP0xHxOr-{J0(Knp{D01JG1MVUp6Lj#Q&6Lg<40y#kW~Y zO6${64WQHlKU`agTAl|N!WUbOS)#+Hk@aNkgp^fs24zkFh=AaXVXssT^QOk8i|jND zZd(sucQ&w}TNq$|*nfMv@b&F0jNL|C@0wFVLNQ*2oQ`xz=cq42ty&g;OAI5dpa+2IK8E|+J>7$QJ;(zCN>9Ub5PNuG}(QoN%F_d#B{DbN{n;9^J)frH&FCMuP2cAX;nyY(j zC-gCNV0Z*Ww0fIayQd0kn!pQ)C;PL`v>IaT>^tuFfHm9%%NMtlO%2sUV z#pRz_tH~jU58(AU+m{HWpesxJ6+E4(=Tq=toGo<0XC=)bMJUDO;-PK#F7Z*G?CYU# z3!d+Ah6?~wQaPc{&jX7_J#C-=Nvl^0KE}o=nts2=iBDnm=uqc4$17+vO%MnBaNoo$ zG1@ziE5!{o%9_IE3PJINP;fir^2kLl|5N{)^Evv}U6V)2c) zhM7^#k90Pf`)sWNYtQ^!L{xS@J`dKlI(4pz9i`Wso|#S$LN^y#@B5`TZbY{YFL?C; zP?e`_Kfe8`_ATW&4^VB9V^9R*^s8o@A0rd4DM5`m3yuKW?rzJV`2~7$Z19kyG$Um^ z@viOIupcPx0WwZOR806`+O^)f;lIcS{M+U@dEGEhKb;qF)?wYoV&M7Z8YYwtSCDHY9xWV5vEkN>kfv0-8=8vRAoyy^<;Q~@Bk zh$dL_sCw>V_dOIbc0TUsbBaiS7Uy!t&O&7{rvJ2Oe&mWmpR>&ks;>A8a7vQ{_Yf^7&m2dSKxU!}5P&hy?QLS*_mw0!SM3 z`isYGGe<$+n-4Hs8g0J0d>72v#^lv1 z^W&Cp{sj^UK6|%_a7&&qVxso4p6*-(Cr@R8xUKMa><&EUiC$Et3$Rdh3;vNoOjxZZ zz{RR=FXp&xH1-#K<`;tC_BE%D*R+XO8CX?3tov95+9>~wSz17F0K zD42zDEQB!Ln-R|l)awnoq6nfvkq@R~aP5=Zblm9Fcq``gDyOYk<#5Djv)xpiW6Cqs zL#~ueA&o$Rsm8c?xX@QGOAn_FAu&O>Ph8nNy(Uf@TdKgKs+_w)_yueox_ZB&9sHA~ zfjr~*!{vS!^EV8ARImyfO&Ww+VI=^J^75~?+qG#+tDxj zInOq3JHb*RC(mf>bBmcjHFxSQ;5A=|YtODNl^X2yfQgp!bK(9|k<;_*JUa)F1SceR z<+U5~UIGx3km>{$QI^;2DNLDyMEj$rN;5?ho{lARdQYj_Hk9#N>*M&;Gda#p%VhN5 z{@xpwmw7q)wCAbx?v!u2z|$A?+R#FeWSw@J$A6A<(m%QWm`-H^ty68eYuyhvDgP2F|1I{92<3g~r^k#3 zU#~l;a*{#j_mg3=Xz9A-n8vQ@ZPDQ9GSGe}EzNf~;*u#{)N#boQ# z+x7t5RCIAIEy?j(ek2u)_gZ!>T7@|fY>0w@gf+1LK66B3^*G$y%RP3mG|B65WX?9Gld`U!DTAf}9tQ`z92bs;{_vpR3LXQ(r}exz%S{M?O&L784CoV)>kA@N!HV zqk~b$c-d0@0m0qE;ls|9D@#+?2mwAgK#W;6q^ezI5h&7^U7GodK=`R$)%nTpU}#cG z-kqZaJ$jRTCO&NvH31siynoAvksjww7jm+0K zsu^>z*A3rhusO3!w?(;NW%4?54`is&3PI*GPPTlCPZbi(N(`0aiO@>shkLjnG?Sm{+t}Te90Aj=*aGLf zaQG@%9HpviTBpHGD@Py%YVBA7bkL(~qx%o`+-iMN!Qs1V~C zzk}?LM#@GRG3@>{O`Q#Jl9Y(lGemM+{t~g9y1ZFL7W;2+8h6F60Ag*NE*%c*ST2J} zcUt6&*ZpKr*>JuI8i_Zze)dV#Q+j>(GBl7IrgAiqVdPtZCBMz}6e%`RV2|xhaAz1` z3GagdSq!%95L^sE8%P;veS=wx^77egH^{8@hnyKcQ3FuD4ht9Fo_hH=$OVw!|H9#m z+`0`4V?6WO+8ZkmTRdE$f(2tA-|Uw|() z>2=GTYRho3n+JVf-NPDwnBvH0GX&0nFWg&r@B4HtC&cOgE*!(5n8QOXja_`8e@*Wp zo5}>fmQ2{7=ojAa#FwC_ZXW_i>anT!>DBy2f8T-gKnv~coHn?ozf}wMYP@1{-qi=r z*mL?lGmGt&U48DQs#Tt%%^kPI$t{Z9!PM^*`(YfYAuO+^_bC~ zVytD{F6H98tMZC$LTcKz!3n34@)G)Xw=ZK5ge|Sza+N#ulk=Lu_lk}Ewu z)uQT44C_7xoHbo)pvMCl);*G0>!*t5n!pdB%(=$DrVV^t^4PrzR^>AtDLWzTP3T|( zt9%-3?t+|1_G|F43iuWvcgTqkPxfzp27aC(%4=G4Hn|`Gvs*j{SUqmnves2#g6@FT zH4lmr{$AFT1r|ZrD{dX%0oK~rSG7TkjR=I- wX<#`1hz2$ylv`Domg0XTQXl?TD7&di>Zy!!Ie#4md`Jk<)z&|khq4a+FF=!3d;kCd literal 0 HcmV?d00001 diff --git a/lib/activate_token.dart b/lib/activate_token.dart index a5c4433..4f43897 100644 --- a/lib/activate_token.dart +++ b/lib/activate_token.dart @@ -1,20 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'main.dart'; +import 'dart:convert'; // Пакет для обработки json с ответом от сервера. + +/// TODO: Вот это все ерунда конечно, это должно обрабатыватсья через state экрана регистрации. -/// Экран регистрации магазина и кассы. class FinishRegistrationScreen extends StatefulWidget { @override State createState() => new _RegistrationScreenState(); } class _RegistrationScreenState extends State { + bool _tokenActive = false; + String _merchantID = null; + @override Widget build(BuildContext context) { return new Scaffold(appBar: _getAppBar(), body: _getScreen(context)); } AppBar _getAppBar() { - return new AppBar(title: new Text("Регистрация магазина"), + return new AppBar(title: new Text("Регистрация", style: new TextStyle(fontSize: 18.0)), backgroundColor: primaryColor, actions: [ new IconButton( icon: new Icon(Icons.help_outline), @@ -23,67 +28,97 @@ class _RegistrationScreenState extends State { } Widget _getScreen(BuildContext context) { - return new Center(child: new Column(children: [ + if (_merchantID == null) { + _getSavedMerchantID(); + } + return new Column(children: [ _getLogo(), + _getMerchantIDTitle(), _getDecoratedText(), _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)); + _getLogo() { + double containerHeight = 92.0; + double imageWidth = 156.0; + return new Container(height: containerHeight, child: new Image.asset(logo_png, width: imageWidth)); } - Container _getDecoratedText() { + _getMerchantIDTitle() { + return new Container(margin: new EdgeInsets.only(top: 8.0, bottom: 8.0, left: 28.0, right: 28.0), + child: new Row(crossAxisAlignment: CrossAxisAlignment.start, + children: [new Text('ID Магазина', textAlign: TextAlign.left, style: new TextStyle(fontWeight: FontWeight.w300, color: greyTextColor, fontSize: 14.0))])); + } + + _getDecoratedText() { return new Container(margin: new EdgeInsets.only(left: 28.0, right: 28.0), padding: new EdgeInsets.only(top: 12.0, bottom: 12.0, left: 16.0, right: 16.0), decoration: _getDecoraionForMerchantId(), - child: _getMerchantIDText()); + child: new Row(children: [_getMerchantIDText()])); } - Text _getMerchantIDText() { - return new Text(merchantID, style: new TextStyle(color: const Color(0xffa5a5a5), fontSize: 16.0)); + _getMerchantIDText() { + return 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); + }); + }); } 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('Запрос на активацию программы отправлен, дождrитесь подтверждения активации администратором', - textAlign: TextAlign.center, style: new TextStyle(fontWeight: FontWeight.bold, color: const Color(0xff4e3a19))))); + 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), + child: new Center(child: new Text(_tokenActive ? 'Программа активирована' : 'Запрос на активацию программы отправлен, дождитесь подтверждения активации администратором', + textAlign: TextAlign.center, style: new TextStyle(height: 1.5, fontWeight: FontWeight.bold, fontSize: 14.0, color: _tokenActive ? tokenActiveTextColor : tokenActivateTextColor)))); } Decoration _getDecoraionForMessageField() { return new BoxDecoration(image: new DecorationImage( - image: new ExactAssetImage(activate_token_bg_png), fit: BoxFit.fill)); + image: new ExactAssetImage(_tokenActive ? active_token_bg_png : activate_token_bg_png), fit: _tokenActive ? BoxFit.fitWidth : BoxFit.fill)); } Decoration _getDecoraionForMerchantId() { - return new BoxDecoration(color: Colors.white, + return new BoxDecoration(color: textFieldBackground, 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: () { - startScanner(context); - }))); + /// Метод возвращает кнопку, которая запускает отправку токена кассы на сервер. + _getButton(BuildContext context) { + 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 ? 'ЗАВЕРШИТЬ РЕГИСТРАЦИЮ' : 'ОБНОВИТЬ СТАТУС АКТИВАЦИИ', + 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']; + }); + + }).catchError((error) { + print(error.toString()); + return false; + }); + } + }, + color: primaryColor)); } } - -_checkToken(BuildContext context) { - checkToken(context, new CheckTokenCallback()); -} - -class CheckTokenCallback extends Callback { - - call(BuildContext context) { - startScanner(); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index c5bfb1e..6afcbb3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'splash.dart'; import 'registration.dart'; import 'dart:async'; import 'dart:convert'; +import 'purchase.dart'; /// Главный класс приложения. /// Здесь распоосложены константы и некоторые методы, которые могут вызываться с разных экранов приложения. @@ -21,56 +22,62 @@ 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'; // Colors const Color primaryColor = const Color(0xffeb0004); -const Color disabledColor = const Color(0xffbfbfbf); +const Color greyTextColor = const Color(0xffa5a5a5); +const Color textBorderColor = const Color(0xffcfd8dc); +const Color textFieldBackground = const Color(0xffefefef); +const Color tokenActiveTextColor = const Color(0xff1f5a1f); +const Color tokenActivateTextColor = const Color(0xff4e3a19); // HttpClient final httpClient = createHttpClient(); -void main() { - runApp(new Checker()); -} - /// Токен кассы. Инициализируется при регистрации. String token; String merchantID = ""; +/// Точка входа в приложение. +void main() { + runApp(new Checker()); +} + /// Проверка статуса токена. Токен может быть активирован, либо не активирован. -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(context); - } - - }).catchError((error) { - print(error.toString()); - }); +checkToken(BuildContext context) async { + return httpClient.get(intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken); } /// Запуск спецефичной для каждой платформы части приложения - сканера. /// Может производиться с нескольких экранов (splash, finish_registration). -startScanner(BuildContext context) async{ +startScanner(BuildContext context) async { const platform = const MethodChannel('com.dinect.checker/instance_id'); + + // Канал слушает ловит вызовы методов из "нативной" части приложения. + // Могут быть вызваны либо logaut либо faq, либо purchase. platform.setMethodCallHandler((MethodCall call) async { - pushRoute(context, new RegistrationScreen()); - return result; - // or - // throw new PlatformException(errorCode, anErrorMessage, someDetails); + + if (call.method == 'foo') { + + String url = intUrl + 'tokens/' + token + '?_dmapptoken=' + intToken; + + httpClient.delete(url).then((response) { + + print(response.body); + + }).catchError((error) { + print(error.toString()); + }); + + pushRoute(context, new RegistrationScreen()); + } else { + pushRoute(context, new PurchaseScreen()); + } + + return result; + }); await platform.invokeMethod('startScanner'); @@ -87,15 +94,11 @@ pushRoute(BuildContext context, Widget widget) { class Checker extends StatelessWidget { @override Widget build(BuildContext context) { - return new MaterialApp(title: "DemoApp", + return new MaterialApp(title: "AutoClub", home: new SplashScreen(), theme: new ThemeData( primaryColor: primaryColor, accentColor: primaryColor )); } -} - -abstract class Callback { - void call(BuildContext context); -} +} \ No newline at end of file diff --git a/lib/purchase.dart b/lib/purchase.dart new file mode 100644 index 0000000..4a3ff70 --- /dev/null +++ b/lib/purchase.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'main.dart'; +import 'dart:async'; +import 'activate_token.dart'; + +/// Экран проведения покупки. +class PurchaseScreen extends StatefulWidget { + @override State createState() => new _PurchaseScreenState(); +} + +class _PurchaseScreenState extends State { + + bool _loading = false; + + @override Widget build(BuildContext context) { + return new Scaffold(appBar: _getAppBar(), body: _getScreen(context)); + } + + AppBar _getAppBar() { + return new AppBar(title: new Text("Проведение покупки"), + actions: [new IconButton(icon: new Icon(Icons.help_outline), onPressed: () {})]); + } + + Widget _getScreen(BuildContext context) { + return new Stack(children: [_getScreenContent(), _getProgressIndicator()]); + } + + Widget _getScreenContent() { + return new Container(height: 332.0, + child: new ListView(reverse: true, children: [ + new Column(children: [ + _getValueWithTitle('ФИО', 'Знаменитый Рокер Паук'), + _getValueWithTitle('Карта', 'B0399900702'), + _getValueWithTitle('Вознаграждение', '100%'), + _getButton(context), + _getButton(context)]) + ].reversed.toList())); + } + + Widget _getProgressIndicator() { + return new Center(child: _loading ? new CircularProgressIndicator() : null); + } + + Widget _getValueWithTitle(String title, String value) { + return new Column(children: [ + new Row(crossAxisAlignment: CrossAxisAlignment.start, children: [new Text(title, textAlign: TextAlign.left, style: new TextStyle(color: greyTextColor, fontSize: 14.0))]), + new Row(crossAxisAlignment: CrossAxisAlignment.start, children: [new Text(value, textAlign: TextAlign.left, style: new TextStyle(color: Colors.black, fontSize: 20.0))]) + ]); + } + + + Widget _getButton(BuildContext context) { + return new Container(margin: new EdgeInsets.only(top: 36.0), height: 42.0, + padding: new EdgeInsets.only(left: 40.0, right: 40.0), + child: new RaisedButton(child: new Text('ЗАРЕГИСТРИРОВАТЬ', + style: new TextStyle(color: Colors.white)), + onPressed: null, + color: primaryColor)); + } + + Widget _getCircularProgressIndicator() { + return new Center(child: new CircularProgressIndicator()); + } + + _register(BuildContext context) async { + // const platform = const MethodChannel('com.dinect.checker/instance_id'); + // String url = intUrl + 'tokens/?_dmapptoken=' + intToken; + // String pos = await platform.invokeMethod('getInstanceID'); + // print(pos); + // String userAgent = 'dm-checker-test v1.0.1'; + + // var body = { + // 'merchant_shop': merchantShop, + // 'pos': pos, + // 'description': userAgent + '-' + pos + // }; + + // print(url); + + // for (var value in body.values) { + // print(value); + // } + + // httpClient.post(url, body: body).then((response) { + // print(response.body); + // Map parsedMap = JSON.decode(response.body); + // token = parsedMap['token']; + // platform.invokeMethod('saveToken', {'token' : token}).then((value) { + // print(value.toString()); + // }); + // setState(() { + // loading = false; + // }); + // pushRoute(context, new FinishRegistrationScreen()); + // }).catchError((error) { + // print(error.toString()); + // }); + } +} \ No newline at end of file diff --git a/lib/registration.dart b/lib/registration.dart index 3c447bd..e620ee2 100644 --- a/lib/registration.dart +++ b/lib/registration.dart @@ -1,10 +1,12 @@ -import 'dart:convert'; +import 'dart:convert'; // Пакет для обработки json с ответом от сервера. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'main.dart'; import 'dart:async'; import 'activate_token.dart'; +/// На фото мой сын, большой любитель голых констант. + /// Экран регистрации магазина и кассы. class RegistrationScreen extends StatefulWidget { @override State createState() => new _RegistrationScreenState(); @@ -12,92 +14,125 @@ class RegistrationScreen extends StatefulWidget { class _RegistrationScreenState extends State { - String _merchantID = ""; + String error = null; bool _loading = false; - @override Widget build(BuildContext context) { + @override build(BuildContext context) { return new Scaffold(appBar: _getAppBar(), body: _getScreen(context)); } - AppBar _getAppBar() { + _getAppBar() { return new AppBar(title: new Text("Регистрация"), actions: [new IconButton(icon: new Icon(Icons.help_outline), onPressed: () {})]); } - Widget _getScreen(BuildContext context) { + _getScreen(BuildContext context) { return new Stack(children: [_getScreenContent(), _getProgressIndicator()]); } - Widget _getScreenContent() { + /// Высота контейнера задана для того, чтобы элементы располагались вверху экрана + /// и список скроллился снизу вверх при открытии клавиатуры. + _getScreenContent() { return new Container(height: 332.0, - child: new ListView(reverse: true, children: [ - new Center(child: new Column(children: [ - _getLogo(), - _getDecoratedInputField(), - _getButton(context)])) - ].reversed.toList())); + child: new ListView(reverse: true, children: [ + new Center(child: new Column(children: [ + _getLogo(), + _getMerchantIDTitle(), + _getDecoratedInputField(), + _getButton(context)])) + ].reversed.toList())); } - Widget _getProgressIndicator() { + /// Индикация отправки токена кассы. + _getProgressIndicator() { return new Center(child: _loading ? new CircularProgressIndicator() : null); } - Widget _getLogo() { - return new Container(height: 192.0, width: 156.0, - child: new Image.asset(logo_png, height: 24.0, width: 156.0)); + /// Метод возвращает контейнер с отступами, который содержит картинку с логотипом. + /// Картинка должна + _getLogo() { + double containerHeight = 162.0; + double imageHeight = 24.0; + double imageWidth = 156.0; + return new Container(height: containerHeight, child: new Image.asset(logo_png, height: imageHeight, width: imageWidth)); } - Widget _getDecoratedInputField() { - return new Container(margin: new EdgeInsets.only(left: 28.0, right: 28.0), - padding: new EdgeInsets.only(top: 12.0, bottom: 12.0, left: 16.0, right: 16.0), - decoration: _getDecoraionForInputField(), - child: _getInputField()); + _getMerchantIDTitle() { + return new Container(margin: new EdgeInsets.only(top: 8.0, bottom: 8.0, left: 28.0, right: 28.0), + child: new Row(crossAxisAlignment: CrossAxisAlignment.start, + children: [new Container(padding: new EdgeInsets.only(right: 8.0), child: new Text(_getMerchantIDTitleText(), overflow: TextOverflow.ellipsis, textAlign: TextAlign.left, + style: new TextStyle(fontWeight: FontWeight.w300, color: error == null ? greyTextColor : primaryColor, fontSize: 14.0)))])); } - Widget _getInputField() { - return new TextField(decoration: new InputDecoration.collapsed(hintText: merchantIDHint, - hintStyle: new TextStyle(color: const Color(0xffa5a5a5), fontSize: 16.0)), - onChanged: (text) => _handleUserInput(text)); - } - - 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))); - } - - Widget _getButton(BuildContext context) { - return new Container(margin: new EdgeInsets.only(top: 36.0), height: 42.0, - padding: new EdgeInsets.only(left: 40.0, right: 40.0), - child: new RaisedButton(child: new Text('ЗАРЕГИСТРИРОВАТЬ', - style: new TextStyle(color: Colors.white)), - onPressed: _isValidMerchantID() ? () => _registerShop(context) : null, - color: primaryColor)); - } - - Widget _getCircularProgressIndicator() { - return new Center(child: new CircularProgressIndicator()); - } - - _isValidMerchantID() { - return merchantID.length == 5; - } - - _handleUserInput(String text) { - if (text.length > 0) { - setState(() { - merchantID = text; - }); + _getMerchantIDTitleText() { + if (merchantID.length == 0 && error == null) { + return ' '; + } else if (error != null) { + return error; + } else { + return 'ID Магазина'; } } - _registerShop(BuildContext context) { + /// Метод возвращает контейнер с установленными отступами, в котором размещен TextField обернутый в BoxDecoration. + _getDecoratedInputField() { + double margin = 28.0; + double verticalPadding = 12.0; + double horizontalPadding = 16.0; + return new Container(margin: new EdgeInsets.only(left: margin, right: margin), + padding: new EdgeInsets.only(top: verticalPadding, bottom: verticalPadding, left: horizontalPadding, right: horizontalPadding), + decoration: _getDecoraionForInputField(), + child: _getInputField()); + } + + /// Метод возвращает TextField для _getDecoratedInputField + _getInputField() { + return new TextField(keyboardType: TextInputType.number, decoration: new InputDecoration.collapsed(hintText: merchantIDHint, + hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), + onChanged: (text) => _handleUserInput(text)); + } + + /// Метод возвращает BoxDecoration для _getDecoratedInputField + _getDecoraionForInputField() { + return new BoxDecoration(color: textFieldBackground, + border: new Border.all(color: textBorderColor, width: 1.0,), + borderRadius: new BorderRadius.all(new Radius.circular(4.0))); + } + + /// Метод возвращает кнопку, которая запускает отправку токена кассы на сервер. + _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() { + return merchantID.length > 0 && merchantID.length < 25; + } + + /// Смена состояния экрана при изменении текста в поле ввода. + _handleUserInput(String text) { setState(() { - _loading = true; - _registerDemo(context); + merchantID = text; }); } + /// Показать индикатор, запросить токен. + _registerShop(BuildContext context) { + setState(() { + _loading = true; + _register(context); + }); + } + + /// Экран зависает на 1 сек, после этого выполняется переход на экран потверждения токена. _registerDemo(BuildContext context) { new Future.delayed(const Duration(milliseconds: 1000), () { _loading = false; @@ -105,38 +140,44 @@ class _RegistrationScreenState extends State { }); } + /// Получение от платформы id установки, формирование запроса на получение токена, сохранение токена. _register(BuildContext context) async { - // const platform = const MethodChannel('com.dinect.checker/instance_id'); - // String url = intUrl + 'tokens/?_dmapptoken=' + intToken; - // String pos = await platform.invokeMethod('getInstanceID'); - // print(pos); - // String userAgent = 'dm-checker-test v1.0.1'; + const platform = const MethodChannel('com.dinect.checker/instance_id'); - // var body = { - // 'merchant_shop': merchantShop, - // 'pos': pos, - // 'description': userAgent + '-' + pos - // }; + String url = intUrl + 'tokens/?_dmapptoken=' + intToken; + String pos = (new DateTime.now().millisecondsSinceEpoch / 1000).toString(); - // print(url); + // Поле description - необязательное. + var body = { + 'merchant_shop': merchantID, + 'pos': pos, + }; - // for (var value in body.values) { - // print(value); - // } + httpClient.post(url, body: body).then((response) { - // httpClient.post(url, body: body).then((response) { - // print(response.body); - // Map parsedMap = JSON.decode(response.body); - // token = parsedMap['token']; - // platform.invokeMethod('saveToken', {'token' : token}).then((value) { - // print(value.toString()); - // }); - // setState(() { - // loading = false; - // }); - // pushRoute(context, new FinishRegistrationScreen()); - // }).catchError((error) { - // print(error.toString()); - // }); + 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 = 'Отсутствует интернет соединение'; + }); + }); } } \ No newline at end of file diff --git a/lib/splash.dart b/lib/splash.dart index b699e82..d7c8afd 100644 --- a/lib/splash.dart +++ b/lib/splash.dart @@ -1,9 +1,10 @@ import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; +import 'dart:async'; +import 'dart:convert'; import 'main.dart'; import 'registration.dart'; import 'activate_token.dart'; -import 'dart:async'; class SplashScreen extends StatelessWidget { @@ -12,6 +13,7 @@ class SplashScreen extends StatelessWidget { // Splash скрин зависает мимнимум на 1 секунду. // После этого начинается проверка токена. + new Future.delayed(const Duration(milliseconds: 1000), () { _showNextScreen(context); }); @@ -24,25 +26,35 @@ class SplashScreen extends StatelessWidget { const platform = const MethodChannel('com.dinect.checker/instance_id'); token = await platform.invokeMethod('getToken'); - + print('token: $token'); // В случае, если в приложении отсутствует токен, // необходимо запустить регистрацию кассы. - // if (token == null) { + + if (token == null) { pushRoute(context, new RegistrationScreen()); - // } else { - // checkToken(context, new CheckTokenCallback()); - // } - } + } else { -} + checkToken(context).then((response) { -class CheckTokenCallback extends Callback { + print(response.body); + Map parsedMap = JSON.decode(response.body); + bool active = parsedMap['active']; - /// Запускается экран ожидания активации токена. - /// В реальности токен активируется в админке вручную, - /// на тестовом сервере токен активируется через несколько минут после создания. - - call(BuildContext context) { - pushRoute(context, new FinishRegistrationScreen()); + if (active) { + Navigator.of(context).pop(); + // Запускается экран сканера, токен кассы активирован, с его помощью можно делать запросы к pos-api. + startScanner(context); + } else { + // Запускается экран ожидания активации токена. + // В реальности токен активируется в админке вручную, + // на тестовом сервере токен активируется через несколько минут после создания. + pushRoute(context, new FinishRegistrationScreen()); + } + + }).catchError((error) { + print(error.toString()); + return false; + }); + } } } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index ec73eea..3d2d690 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ flutter: - assets/splash.png - assets/logout.png - assets/activate_token_message_background.png + - assets/active_token_message_background.png # To add assets from package dependencies, first ensure the asset # is in the lib/ directory of the dependency. Then,