Merge branch 'RG-3445'

This commit is contained in:
Ivan Murashov
2017-09-22 11:58:55 +03:00
64 changed files with 2171 additions and 1294 deletions

View File

@@ -1,12 +1,66 @@
Приложение Checker. #Приложение Checker.
Для запуска необходимо установить [Dart](https://www.dartlang.org/install) - язык программирования и Для запуска необходимо установить [Dart](https://www.dartlang.org/install) - язык программирования и
[flutter](https://flutter.io/setup/) - фреймворк для создания кроссплатформенных мобильных приложений на этом языке. [flutter](https://flutter.io/setup/) - фреймворк для создания кроссплатформенных мобильных приложений на этом языке.
# Перед тем, как собирать приложение, необходимо в файле lib/consts.dart установить правильное значение appName. Для автоклуба - это AutoBonus.
Для сборки и запуска приложения используются команды flutter run (собирает debug apk, устанавливает его на устройство) и Для сборки и запуска приложения используются команды flutter run (собирает debug apk, устанавливает его на устройство) и
flutter build (собирает release apk, не устанавливает на устрйоство). flutter build (собирает release apk, не устанавливает на устройство).
Команды run и build необходимо выполнять с опцией --flavor, чтобы apk файл собирался с необходимыми ресурсами и настройками. Команды run и build необходимо выполнять с опцией --flavor, чтобы apk файл собирался с необходимыми ресурсами и настройками.
Название конкретной flavor передается в аргументе. Все flavors перечислены в файле android/app/build.gradle. Название конкретной flavor передается в аргументе. Все flavors перечислены в файле android/app/build.gradle.
#Добавление брендированного приложения
Для добавления брендированного приложения с названием %name% необходимо:
1) В каталог assets/ положить изображения %name%_logo.png и %name%_splash.png
В качестве splash очень желательно использовать квадрат белого цвета 100x100.
2) В файл pubscpec.yaml в раздел assets/ добавить пути этих изображений.
3) В файл lib/resources.dart в методы ```getPrimaryColor``` и ```getButtonsColor```
добавить цвета необходимые цвета.*
4) В файле consts.dart изменить значения переменных appName, url, appToken на правильные для сборки значения.*
5) В файл android/app/build.gradle в раздел productFlavors добавить блок следующего вида:
```
%name% {
applicationId 'com.dinect.autobonus'
buildConfigField "String", "locale", "\"ru\""
buildConfigField "String", "flavor", "\"%name%\""
buildConfigField "int", "currency", "643"
buildConfigField "String", "supportPhone", "\"8-800-234-6064\""
buildConfigField "String", "supportUrl", "\"https://www.auto-club.biz\""
}
```
где все параметры необходимо заменить на соответствующие приложению значения.
6) В каталог android/app/src/ добавить каталоги %name%/res в которых воссоздать структуру ресурсов аналогичную представленной в каталоге android/app/src/pip/res/:
Каталоги mipmap должны содержать иконки, каталоги values должны содержать .xml файлы с названием приложения в следующем формате:
```
<resources>
<string name="app_name">%name%</string>
</resources>
```
Иконки проще всего нарезать тут:
https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html
В качестве Foreground выбрать иконку приложения размером 512x512, выставить необходимые параметры и скачать архив с нарещанными иконками.
После выполнения всех этих пунктов появится возможность собирать приложение
как описано выше(flutter run --flavor %name% либо flutter build apk --flavor %name%).
\* - параметры из этих пунктов будут при первой же возможности перенесены в пункт 5, чтобы менять их из одного места.
#Добавление локализации приложения
1) В каталог lib/i18n добавить файл messages_%locale%.dart.
Файл делать по аналогии с messages_en.dart. Либо с messages_ru.dart.
2) В каталог android/app/src/main/res добавить каталог values-%locale% с единственным файлом strings.xml.
Файл должен иметь структуру полностью аналогичную файлу android/app/src/main/res/values/strings.xml, измениться должны только значения для строк.

View File

@@ -42,40 +42,22 @@ android {
productFlavors { productFlavors {
autobonus_en { autobonus {
applicationId 'com.dinect.autobonus'
buildConfigField "String", "locale", "\"en\""
buildConfigField "String", "flavor", "\"autobonus\""
}
autobonus_ru {
applicationId 'com.dinect.autobonus' applicationId 'com.dinect.autobonus'
buildConfigField "String", "locale", "\"ru\"" buildConfigField "String", "locale", "\"ru\""
buildConfigField "String", "flavor", "\"autobonus\"" buildConfigField "String", "flavor", "\"autobonus\""
buildConfigField "int", "currency", "643"
buildConfigField "String", "supportPhone", "\"8-800-234-6064\""
buildConfigField "String", "supportUrl", "\"https://www.auto-club.biz\""
} }
autobonus_ua { pip {
applicationId 'com.dinect.autobonus'
buildConfigField "String", "locale", "\"ua\""
buildConfigField "String", "flavor", "\"autobonus\""
}
pip_en {
applicationId 'com.dinect.pip'
buildConfigField "String", "locale", "\"en\""
buildConfigField "String", "flavor", "\"pip\""
}
pip_ru {
applicationId 'com.dinect.pip'
buildConfigField "String", "locale", "\"ru\""
buildConfigField "String", "flavor", "\"pip\""
}
pip_ua {
applicationId 'com.dinect.pip' applicationId 'com.dinect.pip'
buildConfigField "String", "locale", "\"ua\"" buildConfigField "String", "locale", "\"ua\""
buildConfigField "String", "flavor", "\"pip\"" buildConfigField "String", "flavor", "\"pip\""
buildConfigField "int", "currency", "980"
buildConfigField "String", "supportPhone", "\"+38 080 030 9997\\n+38 044 390 1697\""
buildConfigField "String", "supportUrl", "\"http://discount.kiev.ua/\""
} }
} }
@@ -84,20 +66,11 @@ android {
main.jniLibs.srcDir 'jniLibs' main.jniLibs.srcDir 'jniLibs'
pip_ua { pip {
res.srcDirs = ['src/pip/res'] res.srcDirs = ['src/pip/res']
manifest.srcFile 'src/pip/AndroidManifest.xml' manifest.srcFile 'src/pip/AndroidManifest.xml'
} }
pip_ru {
res.srcDirs = ['src/pip/res']
manifest.srcFile 'src/pip/AndroidManifest.xml'
}
pip_en {
res.srcDirs = ['src/pip/res']
manifest.srcFile 'src/pip/AndroidManifest.xml'
}
} }
} }

View File

@@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dinect.checker" package="com.dinect.checker"
android:versionCode="1" android:versionCode="1"
android:versionName="1.0.1"> android:versionName="1.1.0">
<uses-sdk <uses-sdk
android:minSdkVersion="16" android:minSdkVersion="16"
@@ -31,7 +31,7 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@android:style/Theme.Black.NoTitleBar" android:theme="@android:style/Theme.Light.NoTitleBar"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>

View File

@@ -23,27 +23,36 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.dinect.checker.net.ApiClient; import com.dinect.checker.net.ApiClient;
import java.util.Queue; import org.json.JSONArray;
import java.util.concurrent.ArrayBlockingQueue; import org.json.JSONException;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.IOException;
import java.util.Locale;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okhttp3.ResponseBody;
/** /**
@@ -54,26 +63,13 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
private final static String TAG = "Checker.ScannerActivity"; private final static String TAG = "Checker.ScannerActivity";
private static final String[] CLICK_MESSAGES = { private int counter;
"Там ничего нет.",
"Зачем ты это делаешь?",
"Перестань!",
"Ну и зачем?..",
};
private final AtomicInteger counter = new AtomicInteger(0);
private int mColor;
public static final String SCAN_MODES = "SCAN_MODES"; public static final String SCAN_MODES = "SCAN_MODES";
public static final String ERROR_INFO = "ERROR_INFO"; public static final String ERROR_INFO = "ERROR_INFO";
protected AppCompatActivity ctx;
protected ApiClient apiClient;
protected NetworkThread networkThread;
protected DecrementCounterThread counterThread;
protected LogoutDialogFragment logoutDialog; protected LogoutDialogFragment logoutDialog;
protected NotificationThread notificationThread; private ApiClient mClient;
boolean isCameraAvailable() { boolean isCameraAvailable() {
Log.d(TAG, "isCameraAvailable"); Log.d(TAG, "isCameraAvailable");
@@ -86,7 +82,15 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
final Intent response = new Intent(); final Intent response = new Intent();
response.putExtra(ERROR_INFO, message); response.putExtra(ERROR_INFO, message);
setResult(RESULT_CANCELED, response); setResult(RESULT_CANCELED, response);
notificationThread = null; }
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final String appToken = getIntent().getStringExtra(MainActivity.PREF_APP_TOKEN);
final String token = getIntent().getStringExtra(MainActivity.PREF_POS_TOKEN);
final String url = getIntent().getStringExtra(MainActivity.PREF_API_URL) + "/users/";
mClient = new ApiClient(url, appToken, token);
} }
/** /**
@@ -103,9 +107,6 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
cancelRequest("Camera unavailable"); cancelRequest("Camera unavailable");
return false; return false;
} }
ctx = this;
notificationThread = new NotificationThread(this);
notificationThread.start();
// Hide the window title. // Hide the window title.
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(layoutID); setContentView(layoutID);
@@ -115,120 +116,65 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
/** /**
* Configure toolbar of app * Configure toolbar of app
*/ */
protected final void initToolbar(final int toolbarId, final @NonNull String title) { protected final void initToolbar(Intent intent) {
Log.d(TAG, "initToolbar");
final Toolbar toolbar = (Toolbar) findViewById(toolbarId); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
mColor = (int) getIntent().getLongExtra(MainActivity.PREF_APP_BAR_COLOR, 0xffffff); toolbar.setBackgroundColor((int) intent.getLongExtra(MainActivity.PREF_APP_BAR_COLOR, 0xffffff));
toolbar.setBackgroundColor(mColor);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
final ActionBar actionBar = getSupportActionBar(); final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setTitle(title); actionBar.setTitle(null);
actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setDisplayHomeAsUpEnabled(false);
} }
initManualInput();
setupSecretClickHandler(toolbar); setupSecretClickHandler(toolbar);
} }
private void setupSecretClickHandler(final @NonNull Toolbar toolbar) { private void initManualInput() {
EditText manualInput = (EditText) findViewById(R.id.manual_input);
manualInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
handleBarcode(v.getText().toString());
return false;
}
});
}
private void setupSecretClickHandler(final @NonNull View toolbar) {
// Configure increment handler // Configure increment handler
counterThread = new DecrementCounterThread(counter, 700L);
toolbar.setOnClickListener(new View.OnClickListener() { toolbar.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
String toastMessage = null; if (counter == 15) {
switch (counter.incrementAndGet()) {
case 1:
if (false == counterThread.isRunning()) {
counterThread.start();
}
break;
case 5:
toastMessage = CLICK_MESSAGES[0];
break;
case 11:
toastMessage = CLICK_MESSAGES[1];
break;
case 17:
toastMessage = CLICK_MESSAGES[2];
break;
case 23:
toastMessage = null;
counterThread.pause();
Toast.makeText(ctx, CLICK_MESSAGES[3], Toast.LENGTH_SHORT).show();
switchScanner(); switchScanner();
break; } else {
} counter++;
Log.d(TAG, "toolbar clicked " + counter.get() + " times");
if (null != toastMessage) {
notificationThread.addMessage(toastMessage);
} }
Log.d(TAG, "toolbar clicked " + counter + " times");
} }
}); });
} }
private class DecrementCounterThread extends Thread {
final long delay;
final AtomicInteger counter;
private boolean running = false;
private int previous = 0;
public DecrementCounterThread(final AtomicInteger counter, final long delay) {
// Configure decrement handler
Log.d(TAG, "setupSecretClickHandler: DecrementCounterThread()");
this.counter = counter;
this.delay = delay;
}
@Override
public void run() {
running = true;
while (running) {
try {
Thread.sleep(delay);
if (counter.get() > 0) {
if (previous > counter.get()) {
previous = counter.decrementAndGet();
Log.d(TAG, "decrement counter, now " + counter.get());
} else {
previous = counter.get();
}
}
} catch (final IllegalArgumentException | InterruptedException e) {
Log.d(TAG, "disable counter decrease Thread", e);
}
}
}
public boolean isRunning() {
return running;
}
public void pause() {
Log.d(TAG, "pause decrementer");
running = false;
}
}
private void switchScanner() { private void switchScanner() {
final SharedPreferences prefs = getSharedPreferences("MainActivity", Context.MODE_PRIVATE);
int idx = prefs.getInt(MainActivity.SCANNER_BACKEND_KEY, 0);
Log.d(TAG, "current scanner backend " + idx + ", " + MainActivity.SCANNER_BACKEND[idx].toString()); final SharedPreferences prefs = getSharedPreferences("scanner", Context.MODE_PRIVATE);
if (idx >= MainActivity.SCANNER_BACKEND.length - 1) {
idx = 0; int idx = (prefs.getInt(MainActivity.SCANNER_BACKEND_KEY, 0) == MainActivity.ZXING)
} else { ? MainActivity.ZBAR
idx++; : MainActivity.ZXING;
}
Log.d(TAG, "switch to scanner backend " + idx + ", " + MainActivity.SCANNER_BACKEND[idx].toString()); Log.d(TAG, "switch to scanner backend " + idx + ", " + MainActivity.SCANNER_BACKEND[idx].toString());
prefs.edit().putInt(MainActivity.SCANNER_BACKEND_KEY, idx).apply(); prefs.edit().putInt(MainActivity.SCANNER_BACKEND_KEY, idx).apply();
final Intent response = new Intent();
response.putExtra("item", "restartScanner");
cancelRequest("Scanner backend changed"); cancelRequest("Scanner backend changed");
setResult(RESULT_OK);
finish(); finish();
} }
@@ -244,51 +190,60 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
protected abstract View initScanner(); protected abstract View initScanner();
/**
* Initialize network client
*/
protected final void initNetwork(final Intent intent) {
Log.d(TAG, "initNetwork");
final String url = intent.getStringExtra(MainActivity.PREF_API_URL);
final String appToken = intent.getStringExtra(MainActivity.PREF_APP_TOKEN);
final String token = intent.getStringExtra(MainActivity.PREF_POS_TOKEN);
Log.d(TAG, "initializing addScanner activity with url "
+ url + ", appToken " + appToken + ", token " + token);
apiClient = new ApiClient(url, appToken, token);
}
/**
* Handles barcode.
* Makes network call in separate thread and call
* networkResponseCallback
*
* @param card scanned card number
*/
public void handleBarcode(final @NonNull String card) { public void handleBarcode(final @NonNull String card) {
Log.d(TAG, "handleBarcode"); mClient.findUser(card, new Callback() {
notificationThread.addMessage(card); @Override
networkThread = new NetworkThread(this, apiClient); public void onFailure(Call call, IOException e) {
networkThread.card(card).start(); handleFail(card);
} }
protected final void networkResponseCallback(final @NonNull Pair<String, String> result) { @Override
Log.d(TAG, "networkResponseCallback"); public void onResponse(Call call, Response response) throws IOException {
if (null != result.first) { try {
Log.d(TAG, "user found, finish activity with result"); ResponseBody body = response.body();
final Intent intent = new Intent(); if (body != null) {
intent.putExtra("user", result.second); switch (response.code()) {
intent.putExtra("card", result.first); case 200:
setResult(RESULT_OK, intent); final JSONArray users = new JSONArray(body.string());
networkThread.cancel(); if (users.length() > 0) {
notificationThread.cancel(); handleSuccess(card, users.get(0).toString());
finish();
} else { } else {
if (null != result.second) { handleFail(card);
notificationThread.addMessage(result.second); }
break;
case 204:
handleFail(card);
break;
} }
} }
} catch (final IOException | JSONException e) {
Log.e(TAG, e.getMessage(), e);
handleFail(card);
}
}
});
}
protected final void handleSuccess(final String card, final String user) {
runOnUiThread(new Runnable() {
@Override
public void run() {
setResult(RESULT_OK, new Intent().putExtra("user", user).putExtra("card", card));
finish();
}
});
}
protected final void handleFail(final String card) {
runOnUiThread(new Runnable() {
@Override
public void run() {
String message = String.format(getString(R.string.identifier_not_found), card)
+ ".\n"
+ String.format(getString(R.string.error_contact_support), BuildConfig.supportPhone);
Toast.makeText(AbstractScannerActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
} }
@Override @Override
@@ -297,10 +252,25 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
return true; return true;
} }
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.settings).setIcon(getResources().getDrawable(R.drawable.settings));
menu.findItem(R.id.faq).setIcon(getResources().getDrawable(R.drawable.help));
menu.findItem(R.id.logout).setIcon(getResources().getDrawable(R.drawable.logout));
return super.onPrepareOptionsMenu(menu);
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.logout) { if (item.getItemId() == R.id.settings) {
logoutDialog = LogoutDialogFragment.newInstance(mColor); final Intent intent = new Intent();
intent.putExtra("item", "settings");
setResult(RESULT_OK, intent);
finish();
return true;
} else if (item.getItemId() == R.id.logout) {
int color = (int) getIntent().getLongExtra(MainActivity.PREF_APP_BAR_COLOR, 0xffffff);
logoutDialog = LogoutDialogFragment.newInstance(color);
logoutDialog.show(getFragmentManager(), "logout"); logoutDialog.show(getFragmentManager(), "logout");
return true; return true;
} else if (item.getItemId() == R.id.faq) { } else if (item.getItemId() == R.id.faq) {
@@ -366,54 +336,4 @@ public abstract class AbstractScannerActivity extends AppCompatActivity {
return builder.create(); return builder.create();
} }
} }
private class NotificationThread extends Thread {
private final static int MAX_NOTIFICATION_MESSAGES = 2;
private boolean run = true;
private final Queue<String> queue;
private final AbstractScannerActivity ctx;
public NotificationThread(final @NonNull AbstractScannerActivity ctx) {
this.queue = new ArrayBlockingQueue<>(MAX_NOTIFICATION_MESSAGES);
this.ctx = ctx;
}
@Override
public void run() {
while (run) {
if (null != queue.peek()) {
Log.d(TAG, "null != queue.peek()");
final String message = queue.poll();
Log.d(TAG, "message: " + message);
if (null != message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show();
}
});
}
}
try {
Thread.sleep(300);
} catch (final InterruptedException ie) {
run = false;
}
}
}
public void addMessage(final @NonNull String message) {
if (queue.size() == MAX_NOTIFICATION_MESSAGES) {
Log.d(TAG, "Discard message: " + queue.poll());
}
queue.add(message);
Log.d(TAG, "Add message: " + message);
}
public void cancel() {
run = false;
}
}
} }

View File

@@ -1,28 +1,31 @@
package com.dinect.checker; package com.dinect.checker;
import android.os.Bundle;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.dinect.checker.net.ApiClient;
import com.dinect.checker.zbar.CameraActivity; import com.dinect.checker.zbar.CameraActivity;
import com.dinect.checker.zxing.ScannerActivity; import com.dinect.checker.zxing.ScannerActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import io.flutter.app.FlutterActivity; import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;
import java.util.Map;
import java.util.ArrayList;
import java.lang.System;
import java.util.Set;
public class MainActivity extends FlutterActivity { public class MainActivity extends FlutterActivity {
@@ -30,112 +33,109 @@ public class MainActivity extends FlutterActivity {
private static final int START_SCANNER_REQUEST_CODE = 2017; private static final int START_SCANNER_REQUEST_CODE = 2017;
private static final String PREF_POS_MERCHANT_ID = "pref_pos_merchant_id"; public static final String PREF_API_URL = "prefs_api_token";
private static final String PREF_DOC_ID = "pref_doc_id"; public static final String PREF_APP_TOKEN = "pres_app_token";
private static final String PREF_POS_ID = "pref_pos_id"; public static final String PREF_POS_TOKEN = "pref_pos_token";
static final String PREF_API_URL = "prefs_api_token";
static final String PREF_APP_TOKEN = "pres_app_token";
static final String PREF_POS_TOKEN = "pref_pos_token";
static final String PREF_APP_BAR_COLOR = "pref_app_bar_color"; static final String PREF_APP_BAR_COLOR = "pref_app_bar_color";
public static final int ZXING = 0;
public static final int ZBAR = 1;
static final Class[] SCANNER_BACKEND = { static final Class[] SCANNER_BACKEND = {
ScannerActivity.class, ScannerActivity.class,
CameraActivity.class, CameraActivity.class,
}; };
static final String SCANNER_BACKEND_KEY = "scanner_backend_idx"; static final String SCANNER_BACKEND_KEY = "scanner_backend_idx";
private MethodChannel mChannel; private MethodChannel mChannel;
private SharedPreferences mPreferences; private Map mScannerArgs;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this); GeneratedPluginRegistrant.registerWith(this);
initLocale(this);
mPreferences = getPreferences(Context.MODE_PRIVATE);
Log.d(TAG, "application prefs:");
for(final Map.Entry<String, ?> kv: mPreferences.getAll().entrySet()){
Log.d(TAG, " key = " + kv.getKey() + ", value = " + kv.getValue().toString());
}
mChannel = new MethodChannel(getFlutterView(), "com.dinect.checker/instance_id"); mChannel = new MethodChannel(getFlutterView(), "com.dinect.checker/instance_id");
mChannel.setMethodCallHandler( mChannel.setMethodCallHandler(
new MethodCallHandler() { new MethodCallHandler() {
@Override @Override
public void onMethodCall(MethodCall call, Result result) { public void onMethodCall(MethodCall call, Result result) {
callMethod(call, result);
}
});
}
private void callMethod(MethodCall call, Result result) {
switch (call.method) { switch (call.method) {
case "saveToken":
Map tokenArguments = call.arguments();
mPreferences.edit().putString(PREF_POS_TOKEN, (String) tokenArguments.get("token")).apply();
break;
case "getToken":
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 "getLocale": case "getLocale":
result.success(BuildConfig.locale); result.success(getLanguage());
break; break;
case "getFlavor": case "getFlavor":
result.success(BuildConfig.flavor); result.success(BuildConfig.flavor);
break; break;
case "getMerchantID": case "getCurrency":
result.success(mPreferences.getString(PREF_POS_MERCHANT_ID, null)); result.success(BuildConfig.currency);
break; break;
case "startScanner": case "startScanner":
final Map arguments = call.arguments(); mScannerArgs = call.arguments();
final int idx = mPreferences.getInt(SCANNER_BACKEND_KEY, 0); startScannerActivity();
Log.d(TAG, "use " + SCANNER_BACKEND[idx].toString() + " backend, with idx = " + idx);
Intent cameraIntent = new Intent(MainActivity.this, SCANNER_BACKEND[idx]);
cameraIntent.putExtra(PREF_API_URL, (String) arguments.get("url"));
cameraIntent.putExtra(PREF_APP_TOKEN, (String) arguments.get("appToken"));
cameraIntent.putExtra(PREF_POS_TOKEN, (String) arguments.get("token"));
cameraIntent.putExtra(PREF_APP_BAR_COLOR, (Long) arguments.get("color"));
startActivityForResult(cameraIntent, START_SCANNER_REQUEST_CODE);
break;
case "removeKeys":
mPreferences.edit().remove(PREF_POS_TOKEN).apply();
mPreferences.edit().remove(PREF_POS_MERCHANT_ID).apply();
mPreferences.edit().remove(PREF_DOC_ID).apply();
mPreferences.edit().remove(PREF_POS_ID).apply();
result.success(null);
break;
case "getDocID":
int docId = mPreferences.getInt(PREF_DOC_ID, 0) + 1;
mPreferences.edit().putInt(PREF_DOC_ID, docId).apply();
result.success(String.valueOf(docId));
break; break;
case "isOnline": case "isOnline":
boolean online = Utils.isOnline(MainActivity.this); checkInternetConnection(result);
if (!online) {
Toast.makeText(MainActivity.this, "Проверьте интернет соединение", Toast.LENGTH_SHORT).show();
}
result.success(online);
break; break;
case "getPosID": case "getSupportPhone":
String posId = mPreferences.getString(PREF_POS_ID, null); result.success(BuildConfig.supportPhone);
if (posId == null) { break;
posId = String.valueOf(System.currentTimeMillis()); case "getSupportUrl":
} result.success(BuildConfig.supportUrl);
mPreferences.edit().putString(PREF_POS_ID, posId).apply();
result.success(posId);
break; break;
default: default:
result.notImplemented(); result.notImplemented();
break; break;
} }
} }
});
private String getLanguage() {
List<String> availableLanguages = Arrays.asList("ru", "en");
if (availableLanguages.contains(Locale.getDefault().getLanguage())) {
return Locale.getDefault().getLanguage();
} else {
return BuildConfig.locale;
}
}
private void checkInternetConnection(Result result) {
boolean connected = Utils.isOnline(this);
if (!connected)
Toast.makeText(this, "Проверьте интернет соединение", Toast.LENGTH_SHORT).show();
result.success(connected);
}
private void setLocale(String locale) {
Resources res = getResources();
Configuration configuration = new Configuration(res.getConfiguration());
configuration.locale = new Locale(locale);
Locale.setDefault(configuration.locale);
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
private void startScannerActivity() {
final int idx = getSharedPreferences("scanner", Context.MODE_PRIVATE).getInt(SCANNER_BACKEND_KEY, 0);
Intent cameraIntent = new Intent(MainActivity.this, SCANNER_BACKEND[idx]);
cameraIntent.putExtra(PREF_API_URL, (String) mScannerArgs.get("url"));
cameraIntent.putExtra(PREF_APP_TOKEN, (String) mScannerArgs.get("appToken"));
cameraIntent.putExtra(PREF_POS_TOKEN, (String) mScannerArgs.get("token"));
cameraIntent.putExtra(PREF_APP_BAR_COLOR, (Long) mScannerArgs.get("color"));
setLocale((String) mScannerArgs.get("locale"));
startActivityForResult(cameraIntent, START_SCANNER_REQUEST_CODE);
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_CANCELED) { if (requestCode == START_SCANNER_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
finish(); finish();
} else if (requestCode == START_SCANNER_REQUEST_CODE && resultCode == RESULT_OK) { } else if (resultCode == RESULT_OK) {
if (data != null) { if (data != null) {
String user = data.getExtras().getString("user", null); String user = data.getExtras().getString("user", null);
if (user != null) { if (user != null) {
@@ -151,50 +151,26 @@ public class MainActivity extends FlutterActivity {
mChannel.invokeMethod(menuItem, null); mChannel.invokeMethod(menuItem, null);
} }
} }
} else {
startScannerActivity();
} }
} }
} }
public static void initLocale(Context context) {
Resources res = context.getResources();
Configuration configuration = new Configuration(res.getConfiguration());
switch (BuildConfig.locale) {
case "en":
configuration.locale = new Locale("en");
Locale.setDefault(configuration.locale);
res.updateConfiguration(configuration, res.getDisplayMetrics());
break;
case "ru":
configuration.locale = new Locale("ru");
Locale.setDefault(configuration.locale);
res.updateConfiguration(configuration, res.getDisplayMetrics());
break;
case "ua":
configuration.locale = new Locale("ua");
Locale.setDefault(configuration.locale);
res.updateConfiguration(configuration, res.getDisplayMetrics());
break;
}
} }
public void getFlavor() { public void getFlavor() {
} }
public void getCurrency() {
}
public void getLocale() { public void getLocale() {
} }
public void handleItemClick() { public void setUserLocale() {
}
public void getDocID() {
}
public void removeKeys() {
} }
@@ -202,32 +178,16 @@ public class MainActivity extends FlutterActivity {
} }
public void getInstanceID() {
}
public void saveToken() {
}
public void getToken() {
}
public void getPosID() {
}
public void saveMerchantID() {
}
public void getMerchantID() {
}
public void isOnline() { public void isOnline() {
} }
public void getSupportPhone() {
}
public void getSupportUrl() {
}
} }

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2017 .
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dinect.checker;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.Pair;
import com.dinect.checker.net.ApiClient;
/**
* Created by anonymous on 03.08.17.
*/
public final class NetworkThread extends Thread {
private static final String TAG = "Checker.NetworkThread";
private AbstractScannerActivity activity;
private final ApiClient client;
private String card;
/**
* @param activity caller activity (with networkResponseCallback())
* @param client ApiClient instance
*/
public NetworkThread(final @NonNull AbstractScannerActivity activity, final @NonNull ApiClient client) {
this.activity = activity;
this.client = client;
}
@Override
public void run() {
if (null != activity) {
final Pair<String, String> response = client.findUser(activity, card);
Log.d(TAG, "network request done with result: " + response.first);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.networkResponseCallback(response);
}
});
}
}
/**
* Set card for network call
*
* @parm card number to search
*/
public NetworkThread card(final @NonNull String card) {
this.card = card;
return this;
}
void cancel() {
activity = null;
}
}

View File

@@ -11,8 +11,11 @@ public class Utils {
} }
public static boolean isOnline(Context context) { public static boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = getConnectivityManager(context).getActiveNetworkInfo();
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return netInfo != null && netInfo.isConnected(); return netInfo != null && netInfo.isConnected();
} }
private static ConnectivityManager getConnectivityManager(Context context) {
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
} }

View File

@@ -15,101 +15,44 @@
*/ */
package com.dinect.checker.net; package com.dinect.checker.net;
import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log;
import android.util.Pair;
import org.json.JSONArray;
import org.json.JSONException;
import java.lang.String;
import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import okhttp3.Callback;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response;
import com.dinect.checker.R;
/** /**
* Created by anonymous * Created by anonymous
*/ */
public final class ApiClient { public final class ApiClient {
private static final String TAG = "Checker.ApiClient"; private static final String TAG = "Checker.ApiClient";
private static final int TIMEOUT_CONNECTION = 3; private static final int TIMEOUT = 3;
private static final int TIMEOUT_READ = 3;
private static final int TIMEOUT_WRITE = 3;
public final String endpoint; private OkHttpClient mHttp;
public final String appToken; private String mEndpoint;
public final String token;
public ApiClient(final String url, final @NonNull String appToken, final @NonNull String token) {
final OkHttpClient http; mEndpoint = url;
mHttp = new OkHttpClient().
/**
* @param endpoint POS API endpoint
* @param appToken Application Token
* @param token POS token
*/
public ApiClient(final @NonNull String endpoint, final @NonNull String appToken, final @NonNull String token) {
this.endpoint = endpoint;
this.appToken = appToken;
this.token = token;
http = new OkHttpClient().
newBuilder() newBuilder()
.connectTimeout(TIMEOUT_CONNECTION, TimeUnit.SECONDS) .connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT_READ, TimeUnit.SECONDS) .readTimeout(TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT_WRITE, TimeUnit.SECONDS) .writeTimeout(TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(new DinectAuthorizationInterceptor(appToken, token, "checker/0.1", true)) .addInterceptor(new DinectAuthorizationInterceptor(appToken, token, "checker/0.1", true))
.build(); .build();
} }
public void findUser(String card, Callback callback) {
/*** final Request.Builder requestBuilder = new Request.Builder();
* final HttpUrl url = HttpUrl.parse(mEndpoint);
* @param card card/foreigncarf number if (url != null) {
* @return (null, error) on fail or (card, user) info on success HttpUrl.Builder httpBuilder = url.newBuilder().addQueryParameter("auto", card);
*/ mHttp.newCall(requestBuilder.url(httpBuilder.build()).build()).enqueue(callback);
public Pair<String, String> findUser(Context ctx, final @NonNull String card) {
final Request.Builder builder = new Request.Builder();
final Request request;
final HttpUrl.Builder httpBuilder = HttpUrl.parse(endpoint + "/users/").newBuilder();
httpBuilder.addQueryParameter("auto", card);
request = builder
.url(httpBuilder.build())
.build();
try {
final Response response = http.newCall(request).execute();
final String body = response.body().string();
final String NOT_FOUND_MESSAGE = String.format(ctx.getString(R.string.identifier_not_found, card));
switch (response.code()) {
case 200:
final JSONArray users = new JSONArray(body);
if (users.length() > 0) {
return new Pair<>(card, users.get(0).toString());
} else {
return new Pair<>(null, NOT_FOUND_MESSAGE);
}
case 204:
return new Pair<>(null, NOT_FOUND_MESSAGE);
default:
return new Pair<>(null, "Что-то пошло не так");
}
} catch (final IOException | JSONException e) {
Log.e(TAG, e.getMessage(), e);
return new Pair<>(null, "Упс...");
} }
} }
} }

View File

@@ -79,8 +79,6 @@ public final class DinectAuthorizationInterceptor implements Interceptor {
} }
final Request request = requestBuilder.url(url).headers(headers).build(); final Request request = requestBuilder.url(url).headers(headers).build();
final Response response = chain.proceed(request); return chain.proceed(request);
return response;
} }
} }

View File

@@ -1,12 +1,12 @@
package com.dinect.checker.zbar; package com.dinect.checker.zbar;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import com.dinect.checker.AbstractScannerActivity; import com.dinect.checker.AbstractScannerActivity;
@@ -19,7 +19,8 @@ import net.sourceforge.zbar.ImageScanner;
import net.sourceforge.zbar.Symbol; import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet; import net.sourceforge.zbar.SymbolSet;
public class CameraActivity extends AbstractScannerActivity implements Camera.PreviewCallback { public class CameraActivity extends AbstractScannerActivity implements
Camera.PreviewCallback {
public static final String ERROR_INFO = "ERROR_INFO"; public static final String ERROR_INFO = "ERROR_INFO";
@@ -39,12 +40,11 @@ public class CameraActivity extends AbstractScannerActivity implements Camera.Pr
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (!init(R.layout.activity_zbar_scanner)) { if (!init(R.layout.a_zbar)) {
return; return;
} }
initNetwork(getIntent()); initToolbar(getIntent());
initToolbar(R.id.toolbar, getString(R.string.scanner_title));
mPreview = (CameraPreview) initScanner(); mPreview = (CameraPreview) initScanner();

View File

@@ -19,6 +19,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.EditText;
import com.dinect.checker.AbstractScannerActivity; import com.dinect.checker.AbstractScannerActivity;
import com.dinect.checker.R; import com.dinect.checker.R;
@@ -35,23 +36,21 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView;
public class ScannerActivity extends AbstractScannerActivity public class ScannerActivity extends AbstractScannerActivity
implements ZXingScannerView.ResultHandler { implements ZXingScannerView.ResultHandler {
private static final int SCAN_INTERVAL_PERIOD = 2000; private static final int SCAN_INTERVAL_PERIOD = 500;
private ZXingScannerView scannerView; private ZXingScannerView scannerView;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
if (!init(R.layout.activity_zxing_scanner)) {
if (!init(R.layout.a_zxing)) {
return; return;
} }
initNetwork(getIntent());
initToolbar(R.id.zxingToolbar, getString(R.string.scanner_title)); initToolbar(getIntent());
scannerView = (ZXingScannerView) initScanner(); scannerView = (ZXingScannerView) initScanner();
ArrayList<BarcodeFormat> formats = new ArrayList<>();
formats.add(BarcodeFormat.UPC_EAN_EXTENSION);
scannerView.setFormats(formats);
addScanner(scannerView, R.id.zxingRoot); addScanner(scannerView, R.id.zxingRoot);
} }
@@ -81,12 +80,8 @@ public class ScannerActivity extends AbstractScannerActivity
@Override @Override
public void handleResult(Result raw) { public void handleResult(Result raw) {
final String card = raw.getText(); handleBarcode(raw.getText());
scannerView.postDelayed(new Runnable() {
handleBarcode(card);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
scannerView.resumeCameraPreview(ScannerActivity.this); scannerView.resumeCameraPreview(ScannerActivity.this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -41,11 +41,7 @@
android:layout_marginTop="56dp" android:layout_marginTop="56dp"
android:background="#00ff00" /> android:background="#00ff00" />
<android.support.v7.widget.Toolbar <include layout="@layout/v_custom_toolbar" />
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleTextColor="@android:color/white" />
<View <View
android:id="@+id/toolbarShadow" android:id="@+id/toolbarShadow"

View File

@@ -6,11 +6,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/white"> android:background="@android:color/white">
<android.support.v7.widget.Toolbar <include layout="@layout/v_custom_toolbar" />
android:id="@+id/zxingToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleTextColor="@android:color/white" />
<View <View
android:id="@+id/zxingToolbarShadow" android:id="@+id/zxingToolbarShadow"

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleTextColor="@android:color/white">
<EditText
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:id="@+id/manual_input"
android:hint="@string/enter_manual"
android:maxLines="1"
android:inputType="text"
android:imeOptions="actionDone"
android:layout_marginLeft="72dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white"/>
</android.support.v7.widget.Toolbar>

View File

@@ -2,16 +2,27 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_more_vert"
app:showAsAction="ifRoom">
<menu>
<group android:id="@+id/items"/>
<item android:id="@+id/settings"
android:title="@string/settings"
android:icon="@drawable/settings"/>
<item android:id="@+id/faq" <item android:id="@+id/faq"
android:orderInCategory="0"
android:title="@string/faq" android:title="@string/faq"
android:icon="@drawable/help_outline" android:icon="@drawable/help"/>
app:showAsAction="ifRoom"/>
<item android:id="@+id/logout" <item android:id="@+id/logout"
android:orderInCategory="1"
android:title="@string/logout" android:title="@string/logout"
android:icon="@drawable/logout" android:icon="@drawable/logout"/>
app:showAsAction="ifRoom"/>
</menu> </menu>
</item>
</menu>

View File

@@ -2,11 +2,14 @@
<string name="app_name">AutoBonus</string> <string name="app_name">AutoBonus</string>
<string name="scanner_title">Сканер карты</string> <string name="scanner_title">Сканер карты</string>
<string name="scan">Сканировать</string> <string name="scan">Сканировать</string>
<string name="faq">FAQ</string> <string name="faq">Справка</string>
<string name="logout">Выход</string> <string name="logout">Выход</string>
<string name="settings">Настройки</string>
<string name="logout_title">Подтверждение</string> <string name="logout_title">Подтверждение</string>
<string name="logout_text">Вы действительно хотите выйти и ввести другой номер магазина?</string> <string name="logout_text">Вы действительно хотите выйти и ввести другой номер магазина?</string>
<string name="logout_yes">Да</string> <string name="logout_yes">Да</string>
<string name="logout_no">Нет</string> <string name="logout_no">Нет</string>
<string name="identifier_not_found">"Идентификатор %s не найден"</string> <string name="identifier_not_found">"Идентификатор %s не найден"</string>
<string name="enter_manual">Введите штрихкод вручную</string>
<string name="error_contact_support">Можете воспользоваться ручным вводом или позвонить на номер:%s</string>
</resources> </resources>

View File

@@ -2,11 +2,14 @@
<string name="app_name">AutoBonus</string> <string name="app_name">AutoBonus</string>
<string name="scanner_title">Сканер карти</string> <string name="scanner_title">Сканер карти</string>
<string name="scan">Сканувати</string> <string name="scan">Сканувати</string>
<string name="faq">FAQ</string> <string name="faq">Допомога</string>
<string name="logout">Вихід</string> <string name="logout">Вихід</string>
<string name="settings">Налаштування</string>
<string name="logout_title">Підтвердження</string> <string name="logout_title">Підтвердження</string>
<string name="logout_text">Ви дійсно хочете вийти і ввести інший номер магазину?</string> <string name="logout_text">Ви дійсно хочете вийти і ввести інший номер магазину?</string>
<string name="logout_yes">Так</string> <string name="logout_yes">Так</string>
<string name="logout_no">Ні</string> <string name="logout_no">Ні</string>
<string name="identifier_not_found">"Ідентифікатор %s не знайден"</string> <string name="identifier_not_found">"Ідентифікатор %s не знайден"</string>
<string name="enter_manual">Введіть штрихкод вручну</string>
<string name="error_contact_support">Можете скористатися ручним введенням або зателефонувати на номер:\n%s</string>
</resources> </resources>

View File

@@ -2,11 +2,14 @@
<string name="app_name">AutoBonus</string> <string name="app_name">AutoBonus</string>
<string name="scanner_title">Card Scanner</string> <string name="scanner_title">Card Scanner</string>
<string name="scan">Scan</string> <string name="scan">Scan</string>
<string name="faq">FAQ</string> <string name="faq">Help</string>
<string name="logout">Logout</string> <string name="logout">Logout</string>
<string name="settings">Settings</string>
<string name="logout_title">Сonfirmation</string> <string name="logout_title">Сonfirmation</string>
<string name="logout_text">Do you really want to log out and enter a different store number?</string> <string name="logout_text">Do you really want to log out and enter a different store number?</string>
<string name="logout_yes">Yes</string> <string name="logout_yes">Yes</string>
<string name="logout_no">No</string> <string name="logout_no">No</string>
<string name="identifier_not_found">"Identifier %s is not found"</string> <string name="identifier_not_found">"Identifier %s is not found"</string>
<string name="enter_manual">Enter the barcode manually</string>
<string name="error_contact_support">You can use manual input or call the number:\n%s</string>
</resources> </resources>

View File

@@ -1,6 +1,8 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar"/> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColorSecondary">@android:color/white</item>
</style>
</resources> </resources>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dinect.checker"
android:versionCode="1"
android:versionName="1.0.1">
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="21"/>
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".zbar.CameraActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"/>
<activity
android:name=".zxing.ScannerActivity"
android:theme="@style/AppTheme"/>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -4,7 +4,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.2.3' classpath 'com.android.tools.build:gradle:2.3.3'
} }
} }

BIN
assets/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

BIN
assets/help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/settings_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="checker" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="FLUTTER_MODULE_TYPE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

33
ios/Podfile.lock Normal file
View File

@@ -0,0 +1,33 @@
PODS:
- Flutter (1.0.0)
- FMDB (2.7.2):
- FMDB/standard (= 2.7.2)
- FMDB/standard (2.7.2)
- path_provider (0.0.1):
- Flutter
- sqflite (0.0.1):
- Flutter
- FMDB
DEPENDENCIES:
- Flutter (from `/Users/kifio/flutter/bin/cache/artifacts/engine/ios`)
- path_provider (from `/Users/kifio/.pub-cache/hosted/pub.dartlang.org/path_provider-0.2.1+1/ios`)
- sqflite (from `/Users/kifio/.pub-cache/hosted/pub.dartlang.org/sqflite-0.2.2/ios`)
EXTERNAL SOURCES:
Flutter:
:path: /Users/kifio/flutter/bin/cache/artifacts/engine/ios
path_provider:
:path: /Users/kifio/.pub-cache/hosted/pub.dartlang.org/path_provider-0.2.1+1/ios
sqflite:
:path: /Users/kifio/.pub-cache/hosted/pub.dartlang.org/sqflite-0.2.2/ios
SPEC CHECKSUMS:
Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf
FMDB: 6198a90e7b6900cfc046e6bc0ef6ebb7be9236aa
path_provider: f96fff6166a8867510d2c25fdcc346327cc4b259
sqflite: 8e2d9fe1e7cdc95d4d537fc7eb2d23c8dc428e3c
PODFILE CHECKSUM: 351e02e34b831289961ec3558a535cbd2c4965d2
COCOAPODS: 1.2.0

View File

@@ -11,6 +11,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
755861CA44FB15BD2EFD12F7 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28B464359F9DDCC3EF756D7D /* libPods-Runner.a */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
@@ -54,6 +55,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
28B464359F9DDCC3EF756D7D /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -120,12 +122,20 @@
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
BBA9BB5B1F179C320053B6EA /* libzbar.a in Frameworks */, BBA9BB5B1F179C320053B6EA /* libzbar.a in Frameworks */,
755861CA44FB15BD2EFD12F7 /* libPods-Runner.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
578B8FA7D56ACA2E56C02128 /* Pods */ = {
isa = PBXGroup;
children = (
);
name = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -148,6 +158,7 @@
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
BBA9BB001F1786510053B6EA /* Frameworks */, BBA9BB001F1786510053B6EA /* Frameworks */,
578B8FA7D56ACA2E56C02128 /* Pods */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -195,6 +206,7 @@
BBA9BB351F1792690053B6EA /* CoreMedia.framework */, BBA9BB351F1792690053B6EA /* CoreMedia.framework */,
BBA9BB331F17925F0053B6EA /* CoreGraphics.framework */, BBA9BB331F17925F0053B6EA /* CoreGraphics.framework */,
BBA9BB311F1792570053B6EA /* AVFoundation.framework */, BBA9BB311F1792570053B6EA /* AVFoundation.framework */,
28B464359F9DDCC3EF756D7D /* libPods-Runner.a */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -270,12 +282,15 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
586CFDEBE2C82C2A9F7DA22E /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
889CEA9B47E2F8AFF76F6433 /* [CP] Embed Pods Frameworks */,
1F7C9AAD1A9D38F5484B84D1 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@@ -342,6 +357,21 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
1F7C9AAD1A9D38F5484B84D1 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -356,6 +386,36 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
}; };
586CFDEBE2C82C2A9F7DA22E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
889CEA9B47E2F8AFF76F6433 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;

View File

@@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

View File

@@ -17,8 +17,6 @@ extension ZBarSymbolSet: Sequence {
} }
} }
// TODO: Реализовать окно сканнера в этом контроллере, вместо вызова ZBarReaderViewController
@objc class ScannerViewController: UIViewController, ZBarReaderDelegate { @objc class ScannerViewController: UIViewController, ZBarReaderDelegate {
override func viewDidLoad() { override func viewDidLoad() {

13
lib/base/base_screen.dart Normal file
View File

@@ -0,0 +1,13 @@
import 'package:checker/db.dart';
import 'package:flutter/material.dart';
abstract class BaseScreen extends StatefulWidget {
final SqliteHelper helper;
final String app;
BaseScreen(this.helper, this.app);
@override
State<StatefulWidget> createState();
}

View File

@@ -1,16 +1,19 @@
import 'dart:async';
import 'package:checker/resources.dart'; import 'package:checker/resources.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'common.dart'; import 'package:checker/common.dart';
import 'consts.dart'; import 'package:checker/consts.dart';
import 'strings.dart'; import 'package:checker/screens/settings.dart';
import 'package:checker/screens/faq.dart';
import 'package:checker/strings.dart';
import 'package:checker/db.dart';
abstract class BaseState<T extends StatefulWidget> extends State<T> { abstract class BaseState<T extends StatefulWidget> extends State<T> {
/// Класс для работы с бд.
SqliteHelper helper;
/// Тип сборки. Определяет, какие брать ресурсы (цвета, картинки) /// Тип сборки. Определяет, какие брать ресурсы (цвета, картинки)
String app; String app;
@@ -21,24 +24,7 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
String error; String error;
/// Введенное пользователем значение. /// Введенное пользователем значение.
String textFieldValue = ''; String dinCode = '';
@override Widget build(BuildContext ctx) {
platform.invokeMethod('getLocale').then((locale) {
Intl.defaultLocale = locale;
if (app == null) {
platform.invokeMethod('getFlavor').then((flavor) {
setState(() {
app = flavor;
onStart();
});
});
}
});
return getMainWidget();
}
Widget getMainWidget() { Widget getMainWidget() {
return app == null ? getBackground() : new Scaffold(appBar: getAppBar(), return app == null ? getBackground() : new Scaffold(appBar: getAppBar(),
@@ -56,31 +42,63 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
fit: BoxFit.cover))); fit: BoxFit.cover)));
} }
void onStart() {
}
/// Возвращает контейнер с всеми виджетами экрана. /// Возвращает контейнер с всеми виджетами экрана.
Widget getScreenContent(); Widget getScreenContent();
/// Возвращает заголовок для AppBar /// Возвращает заголовок для AppBar
String getTitle(); String getTitle() {
return null;
}
AppBar getAppBar() { AppBar getAppBar() {
return new AppBar(title: new Text(getTitle(), style: new TextStyle(fontSize: 18.0)), return new AppBar(title: new Container(
margin: new EdgeInsets.only(left: 16.0),
child: new Text(getTitle(), style: new TextStyle(fontSize: 18.0))),
backgroundColor: Resources.getPrimaryColor(app), actions: getMenuButtons()); backgroundColor: Resources.getPrimaryColor(app), actions: getMenuButtons());
} }
List<Widget> getMenuButtons() { List<Widget> getMenuButtons() {
return <Widget>[getFaqButton()]; return <Widget>[
new PopupMenuButton<int>(
onSelected: onOptionsItemClick,
itemBuilder: (BuildContext context) {
return [new PopupMenuItem(
value: 0,
child: getMenuItem(settings_png, StringsLocalization.settings())),
new PopupMenuItem(
value: 1,
child: getMenuItem(help_png, StringsLocalization.help())),
new PopupMenuItem(
value: 2,
child: getMenuItem(logout_png, StringsLocalization.logout()))
];
}
)
];
} }
Widget getFaqButton() { void onOptionsItemClick(int index) {
return new IconButton(icon: new Icon(Icons.help_outline), onPressed: () => faq(context, false)); switch (index) {
case 0: {
pushRoute(context, new SettingsScreen(helper, app, false));
break;
}
case 1: {
pushRoute(context, new FAQScreen(false));
break;
}
case 2: {
logout(context, helper);
}
}
} }
Widget getLogoutButton() { /// Возвращает пункт меню (Картинка с текстом)
return new IconButton(icon: new Image.asset(logout_png, height: iconHeight, width: iconHeight), onPressed: () => logout(context)); Widget getMenuItem(String image, String text) {
return new Row(children: [
new Image.asset(image, width: 28.0, height: 28.0),
new Container(padding: new EdgeInsets.only(left: 8.0), child: new Text(text))
]);
} }
/// Возврвщает контейнер, внутри которого Text с подсказкой. /// Возврвщает контейнер, внутри которого Text с подсказкой.
@@ -88,31 +106,31 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
double horizontalMargin = 8.0; double horizontalMargin = 8.0;
return new Container(margin: new EdgeInsets.only(top: horizontalMargin, bottom: horizontalMargin, left: verticalMargin, right: verticalMargin), return new Container(margin: new EdgeInsets.only(top: horizontalMargin, bottom: horizontalMargin, left: verticalMargin, right: verticalMargin),
child: new Row(crossAxisAlignment: CrossAxisAlignment.start, child: new Row(crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[new Text(getHintString(), textAlign: TextAlign.left, children: <Widget>[new Text(getHintOrError(), textAlign: TextAlign.left,
style: new TextStyle(fontWeight: FontWeight.w300, color: error == null ? greyTextColor : Resources.getLogo(app), fontSize: 14.0))])); style: new TextStyle(fontWeight: FontWeight.w300, color: error == null ? greyTextColor : Resources.getPrimaryColor(app), fontSize: 14.0))]));
} }
/// Возвращает подсказку, либо ошибку, если введенные в поле ввода данные неверны. /// Возвращает подсказку, либо ошибку, если введенные в поле ввода данные неверны.
String getHintString() { String getHintOrError() {
if (textFieldValue.length == 0 && error == null) { if (dinCode.length == 0 && error == null) {
return ' '; return ' ';
} else if (error != null) { } else if (error != null) {
return error; return error;
} else { } else {
return getHint(); return getHintString();
} }
} }
/// Возвращает текст подсказки для поля ввода. /// Возвращает текст подсказки для поля ввода.
/// Должен быть переопределен на экранах, на которых есть поле ввода. /// Должен быть переопределен на экранах, на которых есть поле ввода.
String getHint() { String getHintString() {
return null; return null;
} }
/// Смена состояния экрана при изменении текста в поле ввода. /// Смена состояния экрана при изменении текста в поле ввода.
void handleUserInput(String text) { void handleUserInput(String text) {
setState(() { setState(() {
textFieldValue = text; dinCode = text;
}); });
} }
@@ -185,5 +203,4 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
Widget wrapButton(EdgeInsets margin, Widget widget) { Widget wrapButton(EdgeInsets margin, Widget widget) {
return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)])); return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)]));
} }
} }

View File

@@ -0,0 +1,68 @@
import 'package:checker/base/base_state.dart';
import 'package:checker/consts.dart';
import 'package:checker/db.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
abstract class SettingsBaseState<T extends StatefulWidget> extends BaseState<T> {
SettingsBaseState(SqliteHelper helper, String app) {
this.helper = helper;
this.app = app;
}
int selectedItem;
@override Widget build(BuildContext context) {
return new Scaffold(appBar: getAppBar(),
body: getScreenContent());
}
@override
Widget getScreenContent() {
getSelectedValue();
List<Widget> widgets = new List();
for (String option in getOptions()) {
widgets.add(getItem(option));
}
return new ListView(children: widgets);
}
List<String> getOptions();
void saveOption();
void getSelectedValue();
@override
List<Widget> getMenuButtons() {
return null;
}
Widget getItem(String option) {
return new Container(
height: 56.0,
child: (new FlatButton(onPressed: () {
saveOption();
setState(() {
selectedItem = getOptions().indexOf(option);
});
},
child: new Row(children: <Widget>[
new Expanded(child: new Text(option)),
getCheckMark(getOptions().indexOf(option))]))));
}
Widget getCheckMark(int index) {
return index == selectedItem ? new Image.asset(check_png,
width: 28.0,
height: 28.0) : new Image.asset(check_png, color: new Color(0xffffff));
}
@override
String getTitle() {
return StringsLocalization.settings();
}
}

View File

@@ -1,40 +1,50 @@
import 'package:flutter/services.dart'; import 'package:checker/screens/faq.dart';
import 'package:checker/screens/purchase.dart';
import 'package:checker/screens/settings.dart';
import 'package:checker/screens/splash.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'consts.dart'; import 'consts.dart';
import 'db.dart';
import 'network.dart'; import 'network.dart';
import 'resources.dart'; import 'resources.dart';
import 'package:checker/registration.dart';
import 'package:checker/purchase.dart';
import 'faq.dart';
import 'strings.dart'; import 'strings.dart';
// Канал для взаимодействия с кодом платформы. // Канал для взаимодействия с кодом платформы.
const platform = const MethodChannel('com.dinect.checker/instance_id'); const platform = const MethodChannel('com.dinect.checker/instance_id');
// Метод обеспечивает замену текущего объекта route новым. // Метод обеспечивает замену текущего объекта route новым.
pushRoute(BuildContext context, Widget widget) { pushRouteReplacement(BuildContext context, Widget widget) {
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => widget); var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => widget);
Navigator.of(context).pushReplacement(route); Navigator.of(context).pushReplacement(route);
} }
// Добавление route, с возможностью вернуться к предыдущему экрану. pushRoute(BuildContext context, Widget widget) {
faq(BuildContext context, bool returnToScanner) { var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => widget);
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => new FAQScreen(returnToScanner));
Navigator.of(context).push(route); Navigator.of(context).push(route);
} }
// Добавление route, с возможностью вернуться к предыдущему экрану.
faq(BuildContext context, bool returnToScanner) {
pushRoute(context, new FAQScreen(returnToScanner));
}
// В методе отправляется запрос на удаление токена кассы, очищаются SharedPreferences приложения. // В методе отправляется запрос на удаление токена кассы, очищаются SharedPreferences приложения.
logout(BuildContext context) async { logout(BuildContext context, SqliteHelper helper) async {
String token = await platform.invokeMethod('getToken');
VoidCallback positiveCalback = () { String token = await helper.getToken();
String locale = await helper.getLocale();
VoidCallback positiveCallback = () {
if (token != null) { if (token != null) {
deleteToken(token).then((response) { deleteToken(token, locale).then((response) {
print(response.body); helper.clear().then((result) {
platform.invokeMethod('removeKeys').then((result) { helper.close().then((_) {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(); Navigator.of(context).pop();
pushRoute(context, new RegistrationScreen()); // Запускаем регистрацию pushRouteReplacement(context, new SplashScreen()); // Запускаем регистрацию
});
}); });
}).catchError((error) { }).catchError((error) {
print(error.toString()); print(error.toString());
@@ -45,16 +55,22 @@ logout(BuildContext context) async {
} }
}; };
showYesNoDialog(context, StringsLocalization.confirmation(), StringsLocalization.askChangeStore(), positiveCalback); showYesNoDialog(context, StringsLocalization.confirmation(), StringsLocalization.askChangeStore(), positiveCallback);
} }
forceLogout(BuildContext context) async { forceLogout(String token , BuildContext context) async {
String token = await platform.invokeMethod('getToken');
deleteToken(token).then((response) { deleteToken(token, 'ru').then((response) {
print(response.body); SqliteHelper helper = new SqliteHelper();
platform.invokeMethod('removeKeys').then((result) { helper.open().then((_) {
helper.clear().then((_) {
helper.close().then((_) {
while (Navigator.of(context).canPop()) {
Navigator.of(context).pop(); Navigator.of(context).pop();
pushRoute(context, new RegistrationScreen()); // Запускаем регистрацию }
pushRouteReplacement(context, new SplashScreen());
});
});
}); });
}).catchError((error) { }).catchError((error) {
print(error.toString()); print(error.toString());
@@ -63,36 +79,55 @@ forceLogout(BuildContext context) async {
/// Запуск спецефичной для каждой платформы части приложения - сканера. /// Запуск спецефичной для каждой платформы части приложения - сканера.
/// Может производиться с нескольких экранов (splash, finish_registration). /// Может производиться с нескольких экранов (splash, finish_registration).
startScanner(BuildContext context, String app) async { startScanner(BuildContext context, String app, SqliteHelper helper) async {
if (helper == null) {
String token = await platform.invokeMethod('getToken'); helper = new SqliteHelper();
helper.open().then((_) {
startScanner(context, app, helper);
});
} else {
String token = await helper.getToken();
String locale = await helper.getLocale();
helper.close();
// Канал ловит вызовы методов из "нативной" части приложения. // Канал ловит вызовы методов из "нативной" части приложения.
// Могут быть вызваны либо logaut либо faq, либо purchase. // Могут быть вызваны либо logout либо faq, либо purchase.
if (token != null) { if (token != null) {
platform.setMethodCallHandler((MethodCall call) async { platform.setMethodCallHandler((MethodCall call) async {
print('call.method: ${call.method}');
if (call.method == 'logout') { if (call.method == 'logout') {
forceLogout(context); forceLogout(token, context);
} else if (call.method == 'faq') { } else if (call.method == 'faq') {
faq(context, true); faq(context, true);
} else if(call.method == 'settings') {
helper = new SqliteHelper();
helper.open().then((_) {
pushRoute(context, new SettingsScreen(helper, app, true));
});
} else { } else {
String userString = call.arguments[0]; String userString = call.arguments[0];
print('user: ${userString}');
String card = call.arguments[1]; String card = call.arguments[1];
print('card: ${card}'); var route = new MaterialPageRoute<Null>(
var route = new MaterialPageRoute<Null>(builder: (BuildContext context) => new PurchaseScreen(userString, card)); builder: (BuildContext context) =>
new PurchaseScreen(
userString, card));
while (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
Navigator.of(context).pushReplacement(route); Navigator.of(context).pushReplacement(route);
} }
}); });
await platform.invokeMethod('startScanner', { await platform.invokeMethod('startScanner', {
'token': token, 'token': token,
'url': url, 'url': url,
'appToken': appToken, 'appToken': appToken,
'color': Resources.getPrimaryColor(app).value 'locale': locale,
'color': Resources
.getPrimaryColor(app)
.value
}); });
} }
} }
}
// Запуск диалога с двумя кнопками // Запуск диалога с двумя кнопками
showYesNoDialog(BuildContext context, String title, String content, VoidCallback positiveCallback) { showYesNoDialog(BuildContext context, String title, String content, VoidCallback positiveCallback) {
@@ -110,3 +145,22 @@ showYesNoDialog(BuildContext context, String title, String content, VoidCallback
child: new Text(StringsLocalization.yes()), child: new Text(StringsLocalization.yes()),
onPressed: positiveCallback)])); onPressed: positiveCallback)]));
} }
getCurrencyTitle(int code) {
switch(code) {
case 643: return StringsLocalization.nominativeRuble();
case 840: return StringsLocalization.nominativeDollar();
case 980: return StringsLocalization.nominativeHryvna();
case 978: return StringsLocalization.nominativeEuro();
case 398: return StringsLocalization.nominativeTenge();
}
}
getLocaleTitle(String code) {
switch(code) {
case 'ru': return 'Русский';
case 'en': return 'English';
case 'ua': return 'Український';
case 'es': return 'Español';
}
}

View File

@@ -1,16 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// Serious constants // Serious constants
const String appName = "AutoBonus"; const String appName = "Autobonus";
const String url = 'https://pos-api-autoclub.dinect.com/20130701/'; const String url = 'https://pos-api-autoclub.dinect.com/20130701/';
const String appToken = 'bdea0f3ba9034b688019a7cac753d1209e2b227f'; const String appToken = 'bdea0f3ba9034b688019a7cac753d1209e2b227f';
//const String url = 'https://pos-api-int.dinect.com/20130701/';
//const String appToken = '9fec83cdca38c357e6b65dbb17514cdd36bf2a08';
// Assets // Assets
const String logout_png = 'assets/logout.png'; const String logout_png = 'assets/logout.png';
const String help_png = 'assets/help.png';
const String settings_png = 'assets/settings.png';
const String settings_arrow_png = 'assets/settings_arrow.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';
const String expansion_icon_png = 'assets/expansion_icon.png'; const String expansion_icon_png = 'assets/faq_expansion_icon.png';
const String powered_by_dinect_splash_png = 'assets/powered_by_dinect_splash.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'; const String powered_by_dinect_png = 'assets/powered_by_dinect.png';
const String splash_text_png = 'assets/splash_text.png'; const String splash_text_png = 'assets/splash_text.png';

143
lib/db.dart Normal file
View File

@@ -0,0 +1,143 @@
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
/// Данные о таблице сессии пользователя.
const String tableSession = "session";
const String columnMerchantID = "merchant_id"; // DIN code, который вводится при авторизации
const String columnToken = "token"; // Токен для pos. Приходит с бэкэнда.
const String columnPosID = "pos_id"; // идентификатор для создания токена на бэке.
const String columnDocID = "doc_id"; // идентификатор, для проведения покупки на бэкенде.
/// Данные о таблице данных приложения.
const String tableSettings = "settings";
const String columnCurrency = "currency"; // валюта.
const String columnLocale = "locale"; // локаль.
//{
// columnMerchantID: merchantID,
// columnToken: token,
// columnPosID: posID,
// columnDocID: docID
//}
/// База данных, для хранения временных данных (din, token, locale, etc.)
class SqliteHelper {
Database db;
Future open() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "demo.db");
db = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute('''create table $tableSession (
$columnMerchantID text primary key,
$columnToken text,
$columnPosID text,
$columnDocID integer)''');
await db.execute('''create table $tableSettings (
$columnCurrency integer,
$columnLocale text)''');
});
}
/// Создается запись в таблице, содержащая
/// необходимые для идентификации пользователя и проведения запросов.
Future createSession(String merchantID, String posID, String token) async {
Map session = {
columnMerchantID: merchantID,
columnPosID: posID,
columnToken: token,
columnDocID: 0
};
return db.insert(tableSession, session);
}
/// Создается запись в таблице, содержащая данные, которые не зависят от сессии.
Future createAppInfo(String locale, int currency) async {
List<Map> appInfo = await db.query(tableSettings);
if (appInfo.length > 0) {
return null;
} else {
return db.insert(tableSettings, {
columnCurrency: currency
});
}
}
Future<Map> getSettings() async {
return await selectAll(tableSettings);
}
Future<String> getToken() async {
Map session = await selectAll(tableSession);
String token = session != null ? session[columnToken] : null;
return token;
}
Future<String> getMerchantID() async {
Map session = await selectAll(tableSession);
String merchantID = session != null ? session[columnMerchantID] : null;
return merchantID;
}
Future<String> getPosID() async {
Map session = await selectAll(tableSession);
return session != null ? session[columnPosID] : new DateTime.now().millisecondsSinceEpoch.toString();
}
Future<int> getDocID() async {
Map session = await selectAll(tableSession);
int docID = session != null ? session[columnDocID] : 0;
db.update(tableSession, {columnDocID: docID + 1});
return docID;
}
Future<String> getLocale() async {
Map settings = await selectAll(tableSettings);
String locale = settings != null ? settings[columnLocale] : null;
return locale;
}
Future saveLocale(String locale) async {
db.update(tableSettings, {columnLocale: locale});
}
Future<int> getCurrency() async {
Map settings = await selectAll(tableSettings);
int currency = settings != null ? settings[columnCurrency] : null;
return currency;
}
Future saveCurrency(int currency) async {
db.update(tableSettings, {columnCurrency: currency});
}
Future<Map> selectAll(String table) async {
List<Map> maps = await db.query(table, columns: null);
if (maps.length > 0) {
return maps.first;
}
return null;
}
Future clear() async {
return await db.delete(tableSession, where: null);
}
Future close() async => db.close();
}

View File

@@ -1,131 +0,0 @@
import 'package:flutter/material.dart';
import 'base_state.dart';
import 'consts.dart';
import 'common.dart';
/// Класс содержит заголовки и текст блоков FAQ.
class Entry {
Entry(this.title, this.text);
final String title;
final String text;
}
class EntryItem extends StatelessWidget {
const EntryItem(this.entry);
final Entry entry;
Widget _buildTiles(BuildContext context, Entry root) {
EdgeInsets margin = new EdgeInsets.only(left: 20.0, right: 20.0);
TextStyle titleStyle = Theme.of(context).textTheme.button.copyWith(fontWeight: FontWeight.bold, color: faqTitlesColor);
return new Container(margin: margin, child: new Card(child: new ExpansionTile(
key: new PageStorageKey<Entry>(root),
title:new Text(root.title, style: titleStyle),
children: [new Container(margin: margin, padding: new EdgeInsets.only(top: 12.0, bottom: 20.0),
child: new Text(root.text, style: new TextStyle(fontWeight: FontWeight.w300, color: faqGrey, fontSize: 14.0)),
decoration: new BoxDecoration(border: new Border(top: new BorderSide(color: greyTextColor, width: 0.5))))]
)));
}
@override
Widget build(BuildContext context) {
return _buildTiles(context, entry);
}
}
class FAQScreen extends StatefulWidget {
FAQScreen(this.b);
final bool b;
@override State createState() => new FAQScreenState<FAQScreen>(b);
}
class FAQScreenState<T> extends BaseState<FAQScreen> {
FAQScreenState(this.returnToScanner);
final bool returnToScanner;
@override String getTitle() {
return "FAQ";
}
@override getMenuButtons() {
return <Widget>[getLogoutButton()];
}
@override String getHint() {
return null;
}
/// Метод возвращает ListView с блоками faq.
@override Widget getScreenContent() {
return new WillPopScope(onWillPop: onWillPop, child: new ListView.builder(
itemBuilder: (BuildContext context, int index) => new EntryItem(data[index]),
itemCount: data.length));
}
onWillPop() {
if(returnToScanner) {
return startScanner(context, app);
} else {
return true;
}
}
/// Список с контентом
final List<Entry> data = <Entry>[
new Entry('РЕГИСТРАЦИЯ', registrationGuide),
new Entry('ИСПОЛЬЗОВАНИЕ', usageGuide),
new Entry('КОНТАКТЫ ПОДДЕРЖКИ', supportGuide),
new Entry('ОБЩАЯ ИНФОРМАЦИЯ', commonGuide)
];
static const String registrationGuide = '''
После запуска приложения вы окажетесь на странице регистрации магазина.
Введите DIN код магазина (выдается партнером/менеджером International Auto Club, дублируется на почту)
Кликните по кнопке: «Зарегистрировать»
Дождитесь подтверждение активации программы, кликом по кнопке «Обновите статус активации» обновите статус.
После подтверждения запроса на активацию программы Партнером/менеджером кликните по кнопке «Завершить регистрацию», приложение готово к использованию.
При желании изменить номер кассы, необходимо кликнуть на «значок» верхнем правом углу и вернуться на шаг регистрации.
''';
static const String usageGuide = '''
Действие 1:
При предъявлении покупателем штрих-кода участника системы лояльности, запустите данное приложение.
На экране появится сканер штрих кодов. Отсканируйте предъявленный штрих-код сканером.
При успешном сканировании на вашем экране появятся данные партнера.
Действие 2:
Необходимо ввести сумму покупки данного покупателя и кликнуть по кнопке «Зафиксировать».
Всплывет окно подтверждения правильности ввода суммы». В случае правильного ввода суммы, кликните «ДА», сумма будет проведена и вознаграждение будет начислено участнику системы лояльности.
Если сумма введена с ошибкой, кликните «НЕТ» и Вы вернетесь на шаг ввода суммы и сможете её скорректировать.
''';
static const String supportGuide = '''
При некорректной работе приложения AUTO BONUS просьба сразу обратиться по телефону нашей технической поддержки: 8-800-234-6064 (звонок бесплатный) и Вас свяжут с менеджером.
При звонке приготовьтесь назвать ИНН и наименование вашей организации.
Рекомендуйте покупателям установить мобильное приложение дисконтной системы International Auto Club AUTO CLUB и получайте новых лояльных покупателей.
Наш сайт https://www.auto-club.biz
''';
static const String commonGuide = '''
Для эффективного считывания штрих-кода участника системы лояльности необходимо камеру сканера поднести так, чтобы в неё не попадали вертикальные полосы рамки.
Увеличение времени сканирования может произойти из-за черной рамки, в которую помещен штрих-код, так как вертикальные полосы этой рамки расцениваются сканером как часть штрих-кода.
''';
}

View File

@@ -30,6 +30,9 @@ class MessageLookup extends MessageLookupByLibrary {
"no" : MessageLookupByLibrary.simpleMessage("No"), "no" : MessageLookupByLibrary.simpleMessage("No"),
"purchase_complite" : MessageLookupByLibrary.simpleMessage("A purchase of %s USD was complite"), "purchase_complite" : MessageLookupByLibrary.simpleMessage("A purchase of %s USD was complite"),
"registration" : MessageLookupByLibrary.simpleMessage("Registration"), "registration" : MessageLookupByLibrary.simpleMessage("Registration"),
"usage" : MessageLookupByLibrary.simpleMessage("Usage"),
"support" : MessageLookupByLibrary.simpleMessage("Support contacts"),
"common" : MessageLookupByLibrary.simpleMessage("General information"),
"request_sent_wait_activ" : MessageLookupByLibrary.simpleMessage("The activation request for the application has been sent, wait for confirm activation"), "request_sent_wait_activ" : MessageLookupByLibrary.simpleMessage("The activation request for the application has been sent, wait for confirm activation"),
"reward" : MessageLookupByLibrary.simpleMessage("Reward"), "reward" : MessageLookupByLibrary.simpleMessage("Reward"),
"scan" : MessageLookupByLibrary.simpleMessage("Scan"), "scan" : MessageLookupByLibrary.simpleMessage("Scan"),
@@ -38,6 +41,66 @@ class MessageLookup extends MessageLookupByLibrary {
"sum" : MessageLookupByLibrary.simpleMessage("Sum"), "sum" : MessageLookupByLibrary.simpleMessage("Sum"),
"update_activ_status" : MessageLookupByLibrary.simpleMessage("Update activation status"), "update_activ_status" : MessageLookupByLibrary.simpleMessage("Update activation status"),
"user_name" : MessageLookupByLibrary.simpleMessage("User name"), "user_name" : MessageLookupByLibrary.simpleMessage("User name"),
"yes" : MessageLookupByLibrary.simpleMessage("Yes") "yes" : MessageLookupByLibrary.simpleMessage("Yes"),
"settings" : MessageLookupByLibrary.simpleMessage("Settings"),
"help" : MessageLookupByLibrary.simpleMessage("Help"),
"logout" : MessageLookupByLibrary.simpleMessage("Exit"),
"currency" : MessageLookupByLibrary.simpleMessage("Currency"),
"locale" : MessageLookupByLibrary.simpleMessage("Language"),
"ruble" : MessageLookupByLibrary.simpleMessage("Ruble"),
"dollar" : MessageLookupByLibrary.simpleMessage("Dollar"),
"hryvna" : MessageLookupByLibrary.simpleMessage("Hryvna"),
"nominative_ruble": MessageLookupByLibrary.simpleMessage("Ruble"),
"singular_ruble": MessageLookupByLibrary.simpleMessage("Ruble"),
"plural_ruble": MessageLookupByLibrary.simpleMessage("Rubles"),
"nominative_dollar": MessageLookupByLibrary.simpleMessage("US Dollar"),
"singular_dollar": MessageLookupByLibrary.simpleMessage("US Dollar"),
"plural_dollar": MessageLookupByLibrary.simpleMessage("US Dollars"),
"nominative_hryvna": MessageLookupByLibrary.simpleMessage("Hryvnia"),
"singular_hryvna": MessageLookupByLibrary.simpleMessage("Hryvnia"),
"plural_hryvna": MessageLookupByLibrary.simpleMessage("Hryvnia"),
"nominative_tenge": MessageLookupByLibrary.simpleMessage("Tenge"),
"singular_tenge": MessageLookupByLibrary.simpleMessage("Tenge"),
"plural_tenge": MessageLookupByLibrary.simpleMessage("Tenge"),
"nominative_euro": MessageLookupByLibrary.simpleMessage("Euro"),
"singular_euro": MessageLookupByLibrary.simpleMessage("Euro"),
"plural_euro": MessageLookupByLibrary.simpleMessage("Euro"),
"registration_guide": MessageLookupByLibrary.simpleMessage('''
Store log in screen is the first thing you will see after starting the application.
Enter the store number (DIN). You can look it up in the loyalty program control panel. If you do not have access to the control panel, ask the administrator.
Click the ""Login"" button.
Please wait while the administrator activates your request. You can refresh your store activation status by pressing the ""Update activation status"" button.
After the administrator activates your request, click the ""Complete activation"" button. The application is ready to use.
If you want to log in as another store, click the Menu button (upper right corner of the screen) and select "Exit".
'''),
"usage_guide": MessageLookupByLibrary.simpleMessage('''
Step 1:
Launch this application and scan your customer's loyalty card using the built in scanner.
If the scan is successful, the customer's information will appear on the screen.
Step 2:
Enter the purchase amount and click the ""Create a purchase"" button.
In a pop-up window press ""YES"" to confirm the amount and allot the points to a customer.
If you want to correct the amount, press ""NO"" and you will return back to the purchase screen where you can adjust the amount.
'''),
"support_guide": MessageLookupByLibrary.simpleMessage('''
Always recommend your customers to install your loyalty card app, so they can participate in your loyalty program.
If you have any problems with the application, feel free to contact the support.
Phone:\n%s\n
Our website:\n%s'''),
"common_guide": MessageLookupByLibrary.simpleMessage('''
To improve barcode scanning quality, adjust the distance between the camera and the barcode so that the border around the barcode (if any) is not visible. Otherwise the vertical lines of the border could be wrongly considered as part of the code.''')
}; };
} }

View File

@@ -38,6 +38,66 @@ class MessageLookup extends MessageLookupByLibrary {
"sum" : MessageLookupByLibrary.simpleMessage("Suma"), "sum" : MessageLookupByLibrary.simpleMessage("Suma"),
"update_activ_status" : MessageLookupByLibrary.simpleMessage("Actualizar la condición de activación"), "update_activ_status" : MessageLookupByLibrary.simpleMessage("Actualizar la condición de activación"),
"user_name" : MessageLookupByLibrary.simpleMessage("Un nombre de usario"), "user_name" : MessageLookupByLibrary.simpleMessage("Un nombre de usario"),
"yes" : MessageLookupByLibrary.simpleMessage("Si") "yes" : MessageLookupByLibrary.simpleMessage("Si"),
"settings" : MessageLookupByLibrary.simpleMessage("Las configuraciones"),
"help" : MessageLookupByLibrary.simpleMessage("La Ayuda"),
"logout" : MessageLookupByLibrary.simpleMessage("Salir"),
"currency" : MessageLookupByLibrary.simpleMessage("La Moneda"),
"locale" : MessageLookupByLibrary.simpleMessage("La lengua"),
"ruble" : MessageLookupByLibrary.simpleMessage("Ruble"),
"dollar" : MessageLookupByLibrary.simpleMessage("Dollar"),
"hryvna" : MessageLookupByLibrary.simpleMessage("Hryvna"),
"nominative_ruble": MessageLookupByLibrary.simpleMessage("Rublo"),
"singular_ruble": MessageLookupByLibrary.simpleMessage("Rublo"),
"plural_ruble": MessageLookupByLibrary.simpleMessage("Rublos"),
"nominative_dollar": MessageLookupByLibrary.simpleMessage("Dólar Dolares"),
"singular_dollar": MessageLookupByLibrary.simpleMessage("Dólar Dolares"),
"plural_dollar": MessageLookupByLibrary.simpleMessage("Dólar Dolares"),
"nominative_hryvna": MessageLookupByLibrary.simpleMessage("Hryvnia"),
"singular_hryvna": MessageLookupByLibrary.simpleMessage("Hryvnia"),
"plural_hryvna": MessageLookupByLibrary.simpleMessage("Hryvnia"),
"nominative_tenge": MessageLookupByLibrary.simpleMessage("Tenge"),
"singular_tenge": MessageLookupByLibrary.simpleMessage("Tenge"),
"plural_tenge": MessageLookupByLibrary.simpleMessage("Tenge"),
"nominative_euro": MessageLookupByLibrary.simpleMessage("Euro"),
"singular_euro": MessageLookupByLibrary.simpleMessage("Euro"),
"plural_euro": MessageLookupByLibrary.simpleMessage("Euro"),
"registration_guide": MessageLookupByLibrary.simpleMessage('''
Store log in screen is the first thing you will see after starting the application.
Enter the store number (DIN). You can look it up in the loyalty program control panel. If you do not have access to the control panel, ask the administrator.
Click the ""Login"" button.
Please wait while the administrator activates your request. You can refresh your store activation status by pressing the ""Update activation status"" button.
After the administrator activates your request, click the ""Complete activation"" button. The application is ready to use.
If you want to log in as another store, click the Menu button (upper right corner of the screen) and select "Exit".
'''),
"usage_guide": MessageLookupByLibrary.simpleMessage('''
Step 1:
Launch this application and scan your customer's loyalty card using the built in scanner.
If the scan is successful, the customer's information will appear on the screen.
Step 2:
Enter the purchase amount and click the ""Create a purchase"" button.
In a pop-up window press ""YES"" to confirm the amount and allot the points to a customer.
If you want to correct the amount, press ""NO"" and you will return back to the purchase screen where you can adjust the amount.
'''),
"support_guide": MessageLookupByLibrary.simpleMessage('''
Always recommend your customers to install your loyalty card app, so they can participate in your loyalty program.
If you have any problems with the application, feel free to contact the support.
Phone:\n%s\n
Our website:\n%s'''),
"common_guide": MessageLookupByLibrary.simpleMessage('''
To improve barcode scanning quality, adjust the distance between the camera and the barcode so that the border around the barcode (if any) is not visible. Otherwise the vertical lines of the border could be wrongly considered as part of the code.''')
}; };
} }

View File

@@ -25,11 +25,14 @@ class MessageLookup extends MessageLookupByLibrary {
"carry_purchase" : MessageLookupByLibrary.simpleMessage("Проведение покупки"), "carry_purchase" : MessageLookupByLibrary.simpleMessage("Проведение покупки"),
"complite_activ" : MessageLookupByLibrary.simpleMessage("Завершить регистрацию"), "complite_activ" : MessageLookupByLibrary.simpleMessage("Завершить регистрацию"),
"complite_purchase" : MessageLookupByLibrary.simpleMessage("Завершить покупку"), "complite_purchase" : MessageLookupByLibrary.simpleMessage("Завершить покупку"),
"confirm_purchase" : MessageLookupByLibrary.simpleMessage("Вы подтверждаете покупку на %s рублей"), "confirm_purchase" : MessageLookupByLibrary.simpleMessage("Вы подтверждаете покупку на %s %s"),
"confirmation" : MessageLookupByLibrary.simpleMessage("Подтверждение"), "confirmation" : MessageLookupByLibrary.simpleMessage("Подтверждение"),
"no" : MessageLookupByLibrary.simpleMessage("Нет"), "no" : MessageLookupByLibrary.simpleMessage("Нет"),
"purchase_complite" : MessageLookupByLibrary.simpleMessage("Покупка на сумму %s рублей проведена"), "purchase_complite" : MessageLookupByLibrary.simpleMessage("Покупка на сумму %s %s проведена"),
"registration" : MessageLookupByLibrary.simpleMessage("Регистрация"), "registration" : MessageLookupByLibrary.simpleMessage("Регистрация"),
"usage" : MessageLookupByLibrary.simpleMessage("Использование"),
"support" : MessageLookupByLibrary.simpleMessage("Контакты поддержки"),
"common" : MessageLookupByLibrary.simpleMessage("Общая информация"),
"request_sent_wait_activ" : MessageLookupByLibrary.simpleMessage("Запрос на активацию приложения отправлен, дождитесь подтверждения активации администратором"), "request_sent_wait_activ" : MessageLookupByLibrary.simpleMessage("Запрос на активацию приложения отправлен, дождитесь подтверждения активации администратором"),
"reward" : MessageLookupByLibrary.simpleMessage("Вознаграждение"), "reward" : MessageLookupByLibrary.simpleMessage("Вознаграждение"),
"scan" : MessageLookupByLibrary.simpleMessage("Сканировать"), "scan" : MessageLookupByLibrary.simpleMessage("Сканировать"),
@@ -38,6 +41,64 @@ class MessageLookup extends MessageLookupByLibrary {
"sum" : MessageLookupByLibrary.simpleMessage("Сумма"), "sum" : MessageLookupByLibrary.simpleMessage("Сумма"),
"update_activ_status" : MessageLookupByLibrary.simpleMessage("Обновить статус активации"), "update_activ_status" : MessageLookupByLibrary.simpleMessage("Обновить статус активации"),
"user_name" : MessageLookupByLibrary.simpleMessage("ФИО"), "user_name" : MessageLookupByLibrary.simpleMessage("ФИО"),
"yes" : MessageLookupByLibrary.simpleMessage("Да") "yes" : MessageLookupByLibrary.simpleMessage("Да"),
"settings" : MessageLookupByLibrary.simpleMessage("Настройки"),
"help" : MessageLookupByLibrary.simpleMessage("Справка"),
"logout" : MessageLookupByLibrary.simpleMessage("Выход"),
"currency" : MessageLookupByLibrary.simpleMessage("Валюта"),
"locale" : MessageLookupByLibrary.simpleMessage("Язык"),
"nominative_ruble": MessageLookupByLibrary.simpleMessage("Рубль"),
"singular_ruble": MessageLookupByLibrary.simpleMessage("Рубля"),
"plural_ruble": MessageLookupByLibrary.simpleMessage("Рублей"),
"nominative_dollar": MessageLookupByLibrary.simpleMessage("Доллар США"),
"singular_dollar": MessageLookupByLibrary.simpleMessage("Доллара США"),
"plural_dollar": MessageLookupByLibrary.simpleMessage("Долларов США"),
"nominative_hryvna": MessageLookupByLibrary.simpleMessage("Гривна"),
"singular_hryvna": MessageLookupByLibrary.simpleMessage("Гривны"),
"plural_hryvna": MessageLookupByLibrary.simpleMessage("Гривен"),
"nominative_tenge": MessageLookupByLibrary.simpleMessage("Тенге"),
"singular_tenge": MessageLookupByLibrary.simpleMessage("Тенге"),
"plural_tenge": MessageLookupByLibrary.simpleMessage("Тенге"),
"nominative_euro": MessageLookupByLibrary.simpleMessage("Евро"),
"singular_euro": MessageLookupByLibrary.simpleMessage("Евро"),
"plural_euro": MessageLookupByLibrary.simpleMessage("Евро"),
"registration_guide": MessageLookupByLibrary.simpleMessage('''
После запуска приложения вы окажетесь на странице регистрации магазина.
Введите DIN код магазина (выдается партнером/менеджером International Auto Club, дублируется на почту)
Кликните по кнопке: «Зарегистрировать»
Дождитесь подтверждение активации программы, кликом по кнопке «Обновите статус активации» обновите статус.
После подтверждения запроса на активацию программы Партнером/менеджером кликните по кнопке «Завершить регистрацию», приложение готово к использованию.
При желании изменить номер кассы, необходимо кликнуть на «значок» верхнем правом углу и вернуться на шаг регистрации.
'''),
"usage_guide": MessageLookupByLibrary.simpleMessage('''
Шаг 1:
Запустите приложение для сканирования карты участника системы лояльности.
При успешном сканировании на вашем экране появятся данные покупателя.
Шаг 2:
Введите сумму покупки данного покупателя и нажмите на кнопку «Проведение покупки».
Во всплывающем окне нажмите ""ДА"", для подтверждения суммы покупки
Если вы хотите поправить сумму, нажмите «НЕТ» и Вы вернетесь на экран покупки и сможете её скорректировать.
'''),
"support_guide": MessageLookupByLibrary.simpleMessage('''
Рекомендуйте покупателям установить мобильное приложение дисконтной системы и получайте новых лояльных покупателей.
При некорректной работе приложения просьба сразу обратиться по телефону нашей технической поддержки.
Телефон:\n%s\n
Наш сайт:\n%s
'''),
"common_guide": MessageLookupByLibrary.simpleMessage('''
Для эффективного считывания штрих-кода карты участника системы лояльности необходимо камеру сканера поднести так, чтобы в неё не попадали вертикальные полосы рамки (если она есть). Они расцениваются сканером как часть штрих-кода.
''')
}; };
} }

View File

@@ -25,11 +25,14 @@ class MessageLookup extends MessageLookupByLibrary {
"carry_purchase" : MessageLookupByLibrary.simpleMessage("Проведення покупки"), "carry_purchase" : MessageLookupByLibrary.simpleMessage("Проведення покупки"),
"complite_activ" : MessageLookupByLibrary.simpleMessage("Завершити реєстрацію"), "complite_activ" : MessageLookupByLibrary.simpleMessage("Завершити реєстрацію"),
"complite_purchase" : MessageLookupByLibrary.simpleMessage("Завершити купівлю"), "complite_purchase" : MessageLookupByLibrary.simpleMessage("Завершити купівлю"),
"confirm_purchase" : MessageLookupByLibrary.simpleMessage("Ви підтверджуєте покупку на %s гривень"), "confirm_purchase" : MessageLookupByLibrary.simpleMessage("Ви підтверджуєте покупку на %s %s"),
"confirmation" : MessageLookupByLibrary.simpleMessage("Підтвердження"), "confirmation" : MessageLookupByLibrary.simpleMessage("Підтвердження"),
"no" : MessageLookupByLibrary.simpleMessage("Ні"), "no" : MessageLookupByLibrary.simpleMessage("Ні"),
"purchase_complite" : MessageLookupByLibrary.simpleMessage("Купівля на суму %s гривень проведена"), "purchase_complite" : MessageLookupByLibrary.simpleMessage("Купівля на суму %s %s проведена"),
"registration" : MessageLookupByLibrary.simpleMessage("Реєстрація"), "registration" : MessageLookupByLibrary.simpleMessage("Реєстрація"),
"usage" : MessageLookupByLibrary.simpleMessage("Використання"),
"support" : MessageLookupByLibrary.simpleMessage("Контакти підтримки"),
"common" : MessageLookupByLibrary.simpleMessage("Загальна інформація"),
"request_sent_wait_activ" : MessageLookupByLibrary.simpleMessage("Запит на активацію додатку відправлений, дочекайтеся підтвердження активації адміністратором"), "request_sent_wait_activ" : MessageLookupByLibrary.simpleMessage("Запит на активацію додатку відправлений, дочекайтеся підтвердження активації адміністратором"),
"reward" : MessageLookupByLibrary.simpleMessage("Винагорода"), "reward" : MessageLookupByLibrary.simpleMessage("Винагорода"),
"scan" : MessageLookupByLibrary.simpleMessage("Сканувати"), "scan" : MessageLookupByLibrary.simpleMessage("Сканувати"),
@@ -38,6 +41,66 @@ class MessageLookup extends MessageLookupByLibrary {
"sum" : MessageLookupByLibrary.simpleMessage("Сума"), "sum" : MessageLookupByLibrary.simpleMessage("Сума"),
"update_activ_status" : MessageLookupByLibrary.simpleMessage("Оновити статус активації"), "update_activ_status" : MessageLookupByLibrary.simpleMessage("Оновити статус активації"),
"user_name" : MessageLookupByLibrary.simpleMessage("ПІБ"), "user_name" : MessageLookupByLibrary.simpleMessage("ПІБ"),
"yes" : MessageLookupByLibrary.simpleMessage("Так") "yes" : MessageLookupByLibrary.simpleMessage("Так"),
"help" : MessageLookupByLibrary.simpleMessage("Допомога"),
"settings" : MessageLookupByLibrary.simpleMessage("Налаштування"),
"logout" : MessageLookupByLibrary.simpleMessage("Вихід"),
"currency" : MessageLookupByLibrary.simpleMessage("Валюта"),
"locale" : MessageLookupByLibrary.simpleMessage("Мова"),
"nominative_ruble": MessageLookupByLibrary.simpleMessage("Рубль"),
"singular_ruble": MessageLookupByLibrary.simpleMessage("Рубль"),
"plural_ruble": MessageLookupByLibrary.simpleMessage("Рубль"),
"nominative_dollar": MessageLookupByLibrary.simpleMessage("Доллар США"),
"singular_dollar": MessageLookupByLibrary.simpleMessage("Доллар США"),
"plural_dollar": MessageLookupByLibrary.simpleMessage("Доллар США"),
"nominative_hryvna": MessageLookupByLibrary.simpleMessage("Гривня"),
"singular_hryvna": MessageLookupByLibrary.simpleMessage("Гривня"),
"plural_hryvna": MessageLookupByLibrary.simpleMessage("Гривня"),
"nominative_tenge": MessageLookupByLibrary.simpleMessage("Тенге"),
"singular_tenge": MessageLookupByLibrary.simpleMessage("Тенге"),
"plural_tenge": MessageLookupByLibrary.simpleMessage("Тенге"),
"nominative_euro": MessageLookupByLibrary.simpleMessage("Євро"),
"singular_euro": MessageLookupByLibrary.simpleMessage("Євро"),
"plural_euro": MessageLookupByLibrary.simpleMessage("Євро"),
"registration_guide": MessageLookupByLibrary.simpleMessage('''
Після запуску програми ви опинитеся на сторінці реєстрації магазина.
Введіть DIN код магазину (видається при підключенні до системи лояльності)
Натисніть на кнопку «Зареєструвати»
Дочекайтеся підтвердження активації програми, натисканням на кнопку «Оновлення статус активації» поновіть статус.
    
Після підтвердження запиту на активацію програми Партнером / менеджером клікніть по кнопці «Завершити реєстрацію», додаток готове до використання.
При бажанні змінити номер каси, необхідно натиснути на кнопку Меню (верхній правий кут екрану) і вибрати "Вихід".
'''),
"usage_guide": MessageLookupByLibrary.simpleMessage('''
Крок 1:
    
При пред'явленні покупцем картки учасника системи лояльності, запустіть цю програму.
На екрані з'явиться сканер штрих кодів. Відскануте штрих-код карти сканером.
При успішному скануванні на вашому екрані з'являться дані покупця.
    
Крок 2:
    
Необхідно ввести суму покупки даного покупця і клікнути на кнопку «Проведення покупки».
Спливе вікно підтвердження правильності введення суми. У разі правильного введення суми, натисніть «ТАК», сума буде проведена і винагороду буде нараховано учаснику системи лояльності.
Якщо сума введена з помилкою, натисніть «НІ» і Ви повернетеся на крок введення суми і зможете її скорегувати.
'''),
"support_guide": MessageLookupByLibrary.simpleMessage('''
Рекомендуйте покупцям встановити мобільний додаток дисконтної системи і отримуйте нових лояльних покупців.
При некоректній роботі програми прохання відразу звернутися за телефоном нашої технічної підтримки.
Телефон:\n%s\n
Наш сайт:\n%s
'''),
"common_guide": MessageLookupByLibrary.simpleMessage('''
Для ефективного зчитування штрих-коду карти учасника системи лояльності необхідно камеру сканера піднести так, щоб в неї не потрапляли вертикальні смуги рамки. Вони розцінюються сканером як частина штрих-коду.
''')
}; };
} }

View File

@@ -1,44 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'splash.dart'; import 'package:checker/screens/splash.dart';
import 'consts.dart'; import 'consts.dart';
import 'strings.dart';
import 'common.dart';
import 'dart:async';
class StringsLocalizationDelegate extends LocalizationsDelegate<StringsLocalization> {
@override
Future<StringsLocalization> load(Locale locale) async {
return StringsLocalization.load(await platform.invokeMethod("getLocale"));
}
@override
bool shouldReload(LocalizationsDelegate<StringsLocalization> old) {
return false;
}
}
/// Точка входа в приложение. /// Точка входа в приложение.
void main() { void main() {
runApp(new Checker()); runApp(new Checker());
} }
class Checker extends StatefulWidget { // TODO: Запрашивать appName у платформы
@override CheckerState createState() => new CheckerState(); class Checker extends StatelessWidget {
}
class CheckerState extends State<Checker> {
@override Widget build(BuildContext context) { @override Widget build(BuildContext context) {
return new MaterialApp( return new MaterialApp(
title: appName, title: appName,
home: new SplashScreen(), home: new SplashScreen());
localizationsDelegates: getLocalizationsDelegate()
);
}
getLocalizationsDelegate() {
return <StringsLocalizationDelegate>[new StringsLocalizationDelegate()];
} }
} }

View File

@@ -8,7 +8,7 @@ final httpClient = createHttpClient();
// Попытка создать токен для кассы. // Попытка создать токен для кассы.
// В случае если токен для кассы уже существует, вернется ошибка 409. // В случае если токен для кассы уже существует, вернется ошибка 409.
// На сервере есть ограничение в 40 токенов. // На сервере есть ограничение в 40 токенов.
createToken(String merchantId, String posID) async { createToken(String merchantId, String posID, String locale) async {
// Поле description - необязательное. // Поле description - необязательное.
var body = { var body = {
@@ -16,15 +16,15 @@ createToken(String merchantId, String posID) async {
'pos': posID, 'pos': posID,
}; };
return httpClient.post(url + 'tokens/?_dmapptoken=' + appToken, body: body); return httpClient.post(url + 'tokens/?_dmapptoken=' + appToken, body: body, headers: {'Accept-Language': locale});
} }
// Проверка статуса токена. В ответе приходит параметр active, который может быть либо true, либо false,. // Проверка статуса токена. В ответе приходит параметр active, который может быть либо true, либо false,.
checkTokenStatus(String token) async { checkTokenStatus(String token, String locale) async {
return httpClient.get(url + 'tokens/' + token + '?_dmapptoken=' + appToken); return httpClient.get(url + 'tokens/' + token + '?_dmapptoken=' + appToken, headers: {'Accept-Language': locale});
} }
// Удаление токена на сервере. // Удаление токена на сервере.
deleteToken(String token) async { deleteToken(String token, String locale) async {
return httpClient.delete(url + 'tokens/' + token + '?_dmapptoken=' + appToken); return httpClient.delete(url + 'tokens/' + token + '?_dmapptoken=' + appToken, headers: {'Accept-Language': locale});
} }

View File

@@ -1,67 +0,0 @@
import 'package:flutter/material.dart';
import 'package:checker/common.dart';
import 'package:checker/consts.dart';
import 'package:checker/strings.dart';
import 'package:checker/base_state.dart';
/// Экран проведения покупки.
class PurchaseSuccessScreen extends StatefulWidget {
PurchaseSuccessScreen(this.val, this.name);
final String val;
final String name;
@override State createState() => new PurchaseSuccessScreenState(val, name);
}
class PurchaseSuccessScreenState<T> extends BaseState<PurchaseSuccessScreen> {
PurchaseSuccessScreenState(this.sum, this.username);
String sum;
String username;
@override getMenuButtons() {
return <Widget>[getFaqButton(), getLogoutButton()];
}
@override String getTitle() {
return StringsLocalization.carryingPurchase();
}
@override String getHint() {
return null;
}
@override Widget getScreenContent() {
return new Column(children: <Widget>[
getValueWithDescription(StringsLocalization.buyer(), username),
getSuccessMessage(),
new Expanded(child: new Center()),
wrapButton(getScreenMargins(74.0), getScanButton())
]);
}
getScreenMargins(double bottom) {
double side = 42.0;
return new EdgeInsets.only(bottom: bottom, left: side, right: side);
}
getScanButton() {
String title = StringsLocalization.scan();
return buildRaisedButton(title, () => startScanner(context, app));
}
getSuccessMessage() {
return new Row(children: <Widget>[new Expanded(child: new Container(margin: new EdgeInsets.only(top: 20.0), height: 64.0,
decoration: new BoxDecoration(color: greenBackground),
child: new Center(child: new Text(getMessageTitle(), textAlign: TextAlign.center,
style: new TextStyle(fontWeight: FontWeight.bold, color: tokenActiveTextColor)))))]);
}
getMessageTitle() {
return StringsLocalization.purchaseCompleted(sum);
}
}

View File

@@ -0,0 +1,51 @@
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/settings_base_state.dart';
import 'package:checker/db.dart';
import 'package:flutter/material.dart';
import 'package:checker/strings.dart';
class CurrenciesScreen extends BaseScreen {
CurrenciesScreen(helper, app) : super(helper, app);
@override State createState() => new _CurrenciesState(helper, app);
}
class _CurrenciesState extends SettingsBaseState<CurrenciesScreen> {
List<int> currencies = const [643, 840, 980, 978, 398];
_CurrenciesState(SqliteHelper helper, String app) : super(helper, app);
@override
List<String> getOptions() {
String ruble = StringsLocalization.nominativeRuble();
String dollar = StringsLocalization.nominativeDollar();
String hryvna = StringsLocalization.nominativeHryvna();
String euro = StringsLocalization.nominativeEuro();
String tenge = StringsLocalization.nominativeTenge();
return [ruble, dollar, hryvna, euro, tenge];
}
@override
void getSelectedValue() {
helper.getCurrency().then((currency) {
setState(() {
selectedItem = currencies.indexOf(currency);
});
});
}
@override
String getTitle() {
return StringsLocalization.settings();
}
@override
saveOption() async {
await helper.saveCurrency(currencies[selectedItem]);
}
}

143
lib/screens/faq.dart Normal file
View File

@@ -0,0 +1,143 @@
import 'dart:async';
import 'package:checker/resources.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
import 'package:checker/base/base_state.dart';
import 'package:checker/consts.dart';
import 'package:checker/common.dart';
/// Класс содержит заголовки и текст блоков FAQ.
class Entry {
Entry(this.title, this.text);
final String title;
final String text;
}
class EntryItem extends StatelessWidget {
const EntryItem(this.entry);
final Entry entry;
Widget _buildTiles(BuildContext context, Entry root) {
EdgeInsets margin = new EdgeInsets.only(left: 20.0, right: 20.0);
TextStyle titleStyle = Theme.of(context).textTheme.button.copyWith(
fontWeight: FontWeight.bold,
color: faqTitlesColor);
return new Container(margin: margin, child: new Card(
child: new ExpansionTile(
key: new PageStorageKey<Entry>(root),
title:new Text(
root.title,
style: titleStyle),
children: [
new Container(
margin: margin,
padding: new EdgeInsets.only(top: 12.0, bottom: 20.0),
child: new Text(
root.text,
style: new TextStyle(
fontWeight: FontWeight.w300,
color: faqGrey,
fontSize: 14.0)
),
decoration: new BoxDecoration(
border: new Border(
top: new BorderSide(
color: greyTextColor,
width: 0.5)
)
)
)
]
)
)
);
}
@override
Widget build(BuildContext context) {
return _buildTiles(context, entry);
}
}
class FAQScreen extends StatefulWidget {
FAQScreen(this.b);
final bool b;
@override State createState() => new FAQScreenState<FAQScreen>(b);
}
class FAQScreenState<T> extends BaseState<FAQScreen> {
FAQScreenState(this.returnToScanner);
bool returnToScanner;
List<Entry> data;
@override String getTitle() {
return StringsLocalization.help();
}
@override String getHintString() {
return null;
}
@override Widget build(BuildContext context) {
if (app == null) {
platform.invokeMethod('getFlavor').then((flavor) {
initPhoneAndUrl().then((_) {
setState(() {
app = flavor;
});
});
});
}
return new Scaffold(appBar: getAppBar(), body: getScreenContent());
}
Future initPhoneAndUrl() async {
initHelp(await platform.invokeMethod('getSupportPhone'),
await platform.invokeMethod('getSupportUrl'));
}
void initHelp(String phone, String url) {
data = <Entry>[
new Entry(StringsLocalization.registration(), StringsLocalization.registrationGuide()),
new Entry(StringsLocalization.usage(), StringsLocalization.usageGuide()),
new Entry(StringsLocalization.support(), StringsLocalization.supportGuide(phone, url)),
new Entry(StringsLocalization.common(), StringsLocalization.commonGuide())
];
}
@override List<Widget> getMenuButtons() {
return null;
}
/// Метод возвращает ListView с блоками faq.
@override Widget getScreenContent() {
if (data == null) {
return new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new ExactAssetImage(Resources.getSplash(app)),
fit: BoxFit.cover)));
} else {
return new WillPopScope(onWillPop: onWillPop, child: new ListView.builder(
itemBuilder: (BuildContext context, int index) =>
new EntryItem(data[index]),
itemCount: data.length));
}
}
onWillPop() {
if(returnToScanner) {
return startScanner(context, app, helper);
} else {
return true;
}
}
}

View File

@@ -1,34 +1,49 @@
import 'package:flutter/material.dart'; import 'dart:convert';
import 'package:flutter/services.dart';
import 'dart:convert'; // Пакет для обработки json с ответом от сервера.
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/base_state.dart';
import 'package:checker/common.dart'; import 'package:checker/common.dart';
import 'package:checker/consts.dart'; import 'package:checker/consts.dart';
import 'package:checker/db.dart';
import 'package:checker/network.dart'; import 'package:checker/network.dart';
import 'package:checker/base_state.dart';
import 'package:checker/strings.dart'; import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FinishRegistrationScreen extends StatefulWidget { class FinishRegistrationScreen extends BaseScreen {
@override State createState() => new _RegistrationScreenState();
FinishRegistrationScreen(helper, app) : super(helper, app);
@override State createState() => new RegistrationScreenState(helper, app);
} }
class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> { class RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
RegistrationScreenState(SqliteHelper helper, String app) {
this.helper = helper;
this.app = app;
}
bool _tokenActive = false; bool _tokenActive = false;
String _merchantID = ''; String _merchantID = '';
_RegistrationScreenState() { @override Widget build(BuildContext context) {
if (textFieldValue == "") { if (_merchantID == '') {
getSavedMerchantID(); helper.getMerchantID().then((result) {
setState(() {
_merchantID = result;
});
});
} }
return getMainWidget();
} }
@override String getTitle() { @override String getTitle() {
return StringsLocalization.registration(); return StringsLocalization.registration();
} }
@override getHint() { @override getHintString() {
return StringsLocalization.idStore(); return _merchantID;
} }
@override Widget getScreenContent() { @override Widget getScreenContent() {
@@ -47,13 +62,12 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
// Если нет, то отправляется запрос на проверку статуса токена. // Если нет, то отправляется запрос на проверку статуса токена.
handleTap() async { handleTap() async {
if (_tokenActive) { if (_tokenActive) {
startScanner(context, app); startScanner(context, app, helper);
} else { } else {
if (await platform.invokeMethod('isOnline')) { if (await platform.invokeMethod('isOnline')) {
String token = await platform.invokeMethod('getToken'); String token = await helper.getToken();
checkTokenStatus(token).then((response) { String locale = await helper.getLocale();
checkTokenStatus(token, locale).then((response) {
print(response.body);
Map parsedMap = JSON.decode(response.body); Map parsedMap = JSON.decode(response.body);
// Обновить экран, заменить сообщение о необходимости активации токена, на сообщние о том, что токен активен. // Обновить экран, заменить сообщение о необходимости активации токена, на сообщние о том, что токен активен.
@@ -70,23 +84,16 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
} }
@override getTextWidget() { @override getTextWidget() {
return new Row(children: <Widget>[new Text(_merchantID != null ? _merchantID : '', return new Row(
style: new TextStyle(color: Colors.black, fontSize: 16.0))]); children: <Widget>[new Text(_merchantID != null ? _merchantID : '',
} style: new TextStyle(color: Colors.black, fontSize: 16.0))
]);
/// Достаем сохраненный в SharedPreferences merchantID.
getSavedMerchantID() {
platform.invokeMethod('getMerchantID').then((result) {
setState(() {
_merchantID = result;
print('merchanID: ${_merchantID}');
});
});
} }
/// Метод возвращает контейнер с текстом сообщения и бэкграундом. /// Метод возвращает контейнер с текстом сообщения и бэкграундом.
getMessage() { getMessage() {
return new Container(height: _tokenActive ? 72.0 : 108.0, decoration: _getDecorationForMessageField(), return new Container(height: _tokenActive ? 72.0 : 108.0,
decoration: _getDecorationForMessageField(),
margin: new EdgeInsets.only(top: 20.0, left: 12.0, right: 12.0), margin: new EdgeInsets.only(top: 20.0, left: 12.0, right: 12.0),
padding: new EdgeInsets.only(bottom: 22.0, left: 14.0, right: 14.0), padding: new EdgeInsets.only(bottom: 22.0, left: 14.0, right: 14.0),
child: new Center(child: getMessageTextWidget())); child: new Center(child: getMessageTextWidget()));
@@ -95,8 +102,11 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
/// Метод возвращает виджет с текстом сообщения, всеми его привязками и стилями. /// Метод возвращает виджет с текстом сообщения, всеми его привязками и стилями.
getMessageTextWidget() { getMessageTextWidget() {
return new Text(getMessageString(), textAlign: TextAlign.center, return new Text(getMessageString(), textAlign: TextAlign.center,
style: new TextStyle(height: 1.5, fontWeight: FontWeight.bold, fontSize: 14.0, style: new TextStyle(
color: _tokenActive ? tokenActiveTextColor : tokenActivateTextColor)); height: 1.5, fontWeight: FontWeight.bold, fontSize: 14.0,
color: _tokenActive
? tokenActiveTextColor
: tokenActivateTextColor));
} }
/// Получаем текст сообщения, в зависимости от статуса активации. /// Получаем текст сообщения, в зависимости от статуса активации.
@@ -109,7 +119,8 @@ class _RegistrationScreenState extends BaseState<FinishRegistrationScreen> {
/// Фоновое изображение для сообщения. /// Фоновое изображение для сообщения.
Decoration _getDecorationForMessageField() { Decoration _getDecorationForMessageField() {
return new BoxDecoration(image: new DecorationImage( return new BoxDecoration(image: new DecorationImage(
image: new ExactAssetImage(_tokenActive ? active_token_bg_png : activate_token_bg_png), image: new ExactAssetImage(
_tokenActive ? active_token_bg_png : activate_token_bg_png),
fit: _tokenActive ? BoxFit.fitWidth : BoxFit.fill)); fit: _tokenActive ? BoxFit.fitWidth : BoxFit.fill));
} }
} }

View File

@@ -0,0 +1,62 @@
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/settings_base_state.dart';
import 'package:checker/db.dart';
import 'package:flutter/material.dart';
import 'package:checker/strings.dart';
import 'package:checker/common.dart';
import 'package:intl/intl.dart';
class LanguagesScreen extends BaseScreen {
LanguagesScreen(helper, app) : super(helper, app);
@override State createState() => new LanguagesState(helper, app);
}
class LanguagesState extends SettingsBaseState<LanguagesScreen> {
LanguagesState(SqliteHelper helper, String app) : super(helper, app);
List<String> languages = const ['ru', 'en', 'ua', 'es'];
@override
List<String> getOptions() {
List<String> list = new List();
for (String code in languages) {
list.add(getLocaleTitle(code));
}
return list;
}
@override
String getTitle() {
return StringsLocalization.locale();
}
@override
saveOption() async {
await helper.saveLocale(languages[selectedItem]);
Intl.defaultLocale = languages[selectedItem];
await StringsLocalization.load(languages[selectedItem]);
}
@override
void getSelectedValue() {
helper.getLocale().then((locale) {
if (locale == null) {
platform.invokeMethod('getLocale').then((locale) {
setState(() {
selectedItem == locale;
});
});
} else {
setState(() {
selectedItem = getOptions().indexOf(getLocaleTitle(locale));
});
}
});
}
}

View File

@@ -1,15 +1,16 @@
import 'package:checker/db.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';
import 'dart:core'; import 'dart:core';
import 'resources.dart'; import 'package:checker/resources.dart';
import 'strings.dart'; import 'package:checker/strings.dart';
import 'common.dart'; import 'package:checker/common.dart';
import 'consts.dart'; import 'package:checker/consts.dart';
import 'network.dart'; import 'package:checker/network.dart';
import 'base_state.dart'; import 'package:checker/base/base_state.dart';
import 'purchase_success.dart'; import 'package:checker/screens/purchase_success.dart';
/// Экран проведения покупки. /// Экран проведения покупки.
class PurchaseScreen extends StatefulWidget { class PurchaseScreen extends StatefulWidget {
@@ -32,7 +33,23 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
PurchaseScreenState(String userString, String card) { PurchaseScreenState(String userString, String card) {
this.user = JSON.decode(userString); this.user = JSON.decode(userString);
this.card = card; this.card = card;
}
@override Widget build(BuildContext ctx) {
if (helper == null) {
helper = new SqliteHelper();
helper.open().then((_) {
if (app == null) {
platform.invokeMethod('getFlavor').then((flavor) {
app = flavor;
setState(() {
getLoyalty(user['loyalty_url']); getLoyalty(user['loyalty_url']);
});
});
}
});
}
return getMainWidget();
} }
bool purchaseInProgress = false; bool purchaseInProgress = false;
@@ -43,7 +60,7 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
@override Widget getScreenContent() { @override Widget getScreenContent() {
return new Column( return new Column(
children: <Widget>[new Expanded(child: new ListView(children: <Widget>[ children: <Widget>[new Expanded(child: new ListView(children: <Widget>[
getValueWithDescription(StringsLocalization.userName(), user['first_name'] == null ? '' : user['first_name']), getValueWithDescription(StringsLocalization.buyer(), user['first_name'] == null ? '' : user['first_name']),
getValueWithDescription(StringsLocalization.card(), card), getValueWithDescription(StringsLocalization.card(), card),
getValueWithDescription(StringsLocalization.reward(), loyalty), getValueWithDescription(StringsLocalization.reward(), loyalty),
getHintLabel(), getHintLabel(),
@@ -69,7 +86,7 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
child: new Text( child: new Text(
title, title,
style: new TextStyle(color: textColor)), style: new TextStyle(color: textColor)),
onPressed: () => startScanner(context, app)), onPressed: () => startScanner(context, app, helper)),
decoration: new BoxDecoration( decoration: new BoxDecoration(
border: new Border.all(color: Resources.getButtonColor(app), width: 1.0), border: new Border.all(color: Resources.getButtonColor(app), width: 1.0),
borderRadius: new BorderRadius.all(new Radius.circular(4.0)))); borderRadius: new BorderRadius.all(new Radius.circular(4.0))));
@@ -79,19 +96,15 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
return StringsLocalization.carryingPurchase(); return StringsLocalization.carryingPurchase();
} }
@override getHint() { @override getHintString() {
return StringsLocalization.sum(); return StringsLocalization.sum();
} }
@override getMenuButtons() {
return <Widget>[getFaqButton(), getLogoutButton()];
}
@override getTextWidget() { @override getTextWidget() {
return new TextField( return new TextField(
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed( decoration: new InputDecoration.collapsed(
hintText: getHint(), hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0) hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)
), ),
controller: controller, controller: controller,
@@ -109,11 +122,13 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
if (await platform.invokeMethod('isOnline')) { if (await platform.invokeMethod('isOnline')) {
String token = await platform.invokeMethod('getToken'); String token = await helper.getToken();
String locale = await helper.getLocale();
var headers = { var headers = {
'DM-Authorization': 'dmapptoken $appToken', 'DM-Authorization': 'dmapptoken $appToken',
'Authorization': 'dmtoken ${token}' 'Authorization': 'dmtoken ${token}',
'Accept-Language': locale
}; };
httpClient.get(url, headers: headers).then((response) { httpClient.get(url, headers: headers).then((response) {
@@ -127,8 +142,8 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
this.loyalty = '${user['discount']}%'; this.loyalty = '${user['discount']}%';
} else { } else {
List amountToBonus = bonuses['amount_to_bonus']; List amountToBonus = bonuses['amount_to_bonus'];
double loyalityVal = (double.parse(amountToBonus[1]) / amountToBonus[0]) * 100; double loyaltyVal = (double.parse(amountToBonus[1]) / amountToBonus[0]) * 100;
this.loyalty = '${loyalityVal.toStringAsFixed(0)}%'; this.loyalty = '${loyaltyVal.toStringAsFixed(0)}%';
} }
}); });
}).catchError((error) { }).catchError((error) {
@@ -137,6 +152,7 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
} }
} }
// TODO: Переделать? чтобы работало хорошо
String _cleanupNumber(String text){ String _cleanupNumber(String text){
String tmp = text String tmp = text
.replaceAll(' ', '') .replaceAll(' ', '')
@@ -175,9 +191,11 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
onPurchaseClick() { onPurchaseClick() {
String val = _parseSum(controller.text); String val = _parseSum(controller.text);
helper.getCurrency().then((currency) {
print(currency.toString());
showDialog(context: context, child: new AlertDialog( showDialog(context: context, child: new AlertDialog(
title: new Text(StringsLocalization.confirmation()), title: new Text(StringsLocalization.confirmation()),
content: new Text(StringsLocalization.confirmPurchase(val)), content: new Text(StringsLocalization.confirmPurchase(val, currency)),
actions: <Widget>[ actions: <Widget>[
new FlatButton( new FlatButton(
child: new Text(StringsLocalization.no()), child: new Text(StringsLocalization.no()),
@@ -192,6 +210,7 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
}, },
) )
])); ]));
});
} }
purchase(String sumTotal) async { purchase(String sumTotal) async {
@@ -199,34 +218,46 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
if (!purchaseInProgress) { if (!purchaseInProgress) {
purchaseInProgress = true; purchaseInProgress = true;
String token = await platform.invokeMethod('getToken'); String token = await helper.getToken();
platform.invokeMethod('getDocID').then((result) { String locale = await helper.getLocale();
helper.getMerchantID().then((result) {
String url = user['purchases_url']; String url = user['purchases_url'];
helper.getCurrency().then((currency) {
var body = { var body = {
'doc_id': result, 'doc_id': result,
'curr_iso_code': '643', 'curr_iso_code': currency.toString(),
'commit': 'true', 'commit': 'true',
'sum_total': sumTotal 'sum_total': sumTotal
}; };
var headers = { var headers = {
'DM-Authorization': 'dmapptoken $appToken', 'DM-Authorization': 'dmapptoken $appToken',
'Authorization': 'dmtoken ${token}' 'Authorization': 'dmtoken ${token}',
'Accept-Language': locale
}; };
httpClient.post(url, body: body, headers: headers).then((response) { httpClient.post(url, body: body, headers: headers).then((response) {
print(response.body); print(response.body);
Map parsedMap = JSON.decode(response.body);
Navigator.of(context).pop(); Navigator.of(context).pop();
pushRoute(context, new PurchaseSuccessScreen(sumTotal, user['first_name'] == null ? '' : user['first_name']));
if (parsedMap.containsKey('errors')) {
List<String> errors = parsedMap['errors'];
Scaffold.of(context).showSnackBar(new SnackBar(content: new Text(errors[0])));
} else {
pushRouteReplacement(context, new PurchaseSuccessScreen(sumTotal, user['first_name'] == null ? '' : user['first_name'], helper, app));
}
}).catchError((error) { }).catchError((error) {
purchaseInProgress = false; purchaseInProgress = false;
print(error.toString()); print(error.toString());
}); });
}); });
});
} }
} }
} }

View File

@@ -0,0 +1,92 @@
import 'package:checker/base/base_state.dart';
import 'package:checker/common.dart';
import 'package:checker/consts.dart';
import 'package:checker/db.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
/// Экран проведения покупки.
class PurchaseSuccessScreen extends StatefulWidget {
PurchaseSuccessScreen(this.val, this.name, this.helper, this.app);
final String val;
final String name;
final String app;
final SqliteHelper helper;
@override State createState() =>
new PurchaseSuccessScreenState(val, name, helper, app);
}
class PurchaseSuccessScreenState<T> extends BaseState<PurchaseSuccessScreen> {
PurchaseSuccessScreenState(String sum, String username, SqliteHelper helper,
String app) {
this.sum = sum;
this.username = username;
this.helper = helper;
this.app = app;
}
String sum, username;
int currency;
@override String getTitle() {
return StringsLocalization.carryingPurchase();
}
@override String getHintString() {
return null;
}
@override Widget build(BuildContext context) {
if (currency == null) {
helper.getCurrency().then((currency) {
setState(() {
this.currency = currency;
});
});
}
return getMainWidget();
}
@override Widget getScreenContent() {
return new Column(children: <Widget>[
getValueWithDescription(StringsLocalization.buyer(), username),
getSuccessMessage(),
new Expanded(child: new Center()),
wrapButton(getScreenMargins(74.0), getScanButton())
]);
}
getScreenMargins(double bottom) {
double side = 42.0;
return new EdgeInsets.only(bottom: bottom, left: side, right: side);
}
getScanButton() {
String title = StringsLocalization.scan();
return buildRaisedButton(title, () => startScanner(context, app, helper));
}
getSuccessMessage() {
return new Row(children: <Widget>[new Expanded(child: new Container(
margin: new EdgeInsets.only(top: 20.0), height: 64.0,
decoration: new BoxDecoration(color: greenBackground),
child: new Center(
child: new Text(getMessageTitle(), textAlign: TextAlign.center,
style: new TextStyle(fontWeight: FontWeight.bold,
color: tokenActiveTextColor)))))
]);
}
getMessageTitle() {
if (currency != null) {
return StringsLocalization.purchaseCompleted(sum, currency);
} else {
return '';
}
}
}

View File

@@ -1,25 +1,41 @@
import 'package:checker/finish_registration.dart'; import 'dart:convert';
import 'package:flutter/material.dart';
import 'dart:convert'; // Пакет для обработки json с ответом от сервера.
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/base_state.dart';
import 'package:checker/common.dart'; import 'package:checker/common.dart';
import 'package:checker/consts.dart'; import 'package:checker/consts.dart';
import 'package:checker/db.dart';
import 'package:checker/network.dart'; import 'package:checker/network.dart';
import 'package:checker/base_state.dart'; import 'package:checker/screens/finish_registration.dart';
import 'package:checker/strings.dart'; import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
// Пакет для обработки json с ответом от сервера.
/// Экран регистрации магазина и кассы. /// Экран регистрации магазина и кассы.
class RegistrationScreen extends StatefulWidget { class RegistrationScreen extends BaseScreen {
@override State createState() => new _RegistrationScreenState();
RegistrationScreen(helper, app) : super(helper, app);
@override State createState() => new RegistrationScreenState(helper, app);
} }
class _RegistrationScreenState extends BaseState<RegistrationScreen> { class RegistrationScreenState extends BaseState<RegistrationScreen> {
RegistrationScreenState(SqliteHelper helper, String app) {
this.helper = helper;
this.app = app;
}
@override Widget build(BuildContext ctx) {
return getMainWidget();
}
@override String getTitle() { @override String getTitle() {
return StringsLocalization.registration(); return StringsLocalization.registration();
} }
@override getHint() { @override getHintString() {
return StringsLocalization.idStore(); return StringsLocalization.idStore();
} }
@@ -31,13 +47,14 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
getLogo(), getLogo(),
getHintLabel(), getHintLabel(),
getInputField(), getInputField(),
getButton()]) getButton()
])
])); ]));
} }
@override getTextWidget() { @override getTextWidget() {
return new TextField(keyboardType: TextInputType.number, return new TextField(keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed(hintText: getHint(), decoration: new InputDecoration.collapsed(hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
onChanged: (text) => handleUserInput(text)); onChanged: (text) => handleUserInput(text));
} }
@@ -55,8 +72,8 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
/// Токен кассы - это DIN код. DIN код - это специальный код динекта, максимальная его длина - 25 символов. /// Токен кассы - это DIN код. DIN код - это специальный код динекта, максимальная его длина - 25 символов.
_isValidMerchantID() { _isValidMerchantID() {
print("${textFieldValue.length}"); print("${dinCode.length}");
return textFieldValue.length > 0 && textFieldValue.length < 25; return dinCode.length > 0 && dinCode.length < 25;
} }
/// Показать progressBar, запросить токен. /// Показать progressBar, запросить токен.
@@ -70,21 +87,22 @@ class _RegistrationScreenState extends BaseState<RegistrationScreen> {
/// Получение от платформы id установки, формирование запроса на получение токена, сохранение токена. /// Получение от платформы id установки, формирование запроса на получение токена, сохранение токена.
_register() async { _register() async {
if (await platform.invokeMethod('isOnline')) { if (await platform.invokeMethod('isOnline')) {
createToken(textFieldValue, await platform.invokeMethod('getPosID')).then((response) { String posID = await helper.getPosID();
String locale = await helper.getLocale();
createToken(dinCode, posID, locale).then((response) {
setState(() { setState(() {
error = null; error = null;
loading = false; loading = false;
}); });
print(response.body); print(response.body);
print(response.statusCode.toString());
Map parsedMap = JSON.decode(response.body); Map parsedMap = JSON.decode(response.body);
if (response.statusCode == 201) { if (response.statusCode == 201) {
String token = parsedMap['token']; helper.createSession(dinCode, posID, parsedMap['token']).then((_) {
platform.invokeMethod('saveToken', {'token' : token}); pushRouteReplacement(context, new FinishRegistrationScreen(helper, app));
platform.invokeMethod('saveMerchantID', {'merchantID' : textFieldValue}); });
pushRoute(context, new FinishRegistrationScreen());
} else { } else {
setState(() { setState(() {
error = parsedMap['errors'][0]; error = parsedMap['errors'][0];

131
lib/screens/settings.dart Normal file
View File

@@ -0,0 +1,131 @@
import 'package:checker/base/base_screen.dart';
import 'package:checker/base/base_state.dart';
import 'package:checker/common.dart';
import 'package:checker/consts.dart';
import 'package:checker/db.dart';
import 'package:checker/screens/currencies.dart';
import 'package:checker/screens/languages.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class SettingsScreen extends BaseScreen {
final bool returnToScanner;
SettingsScreen(helper, app, this.returnToScanner) : super(helper, app);
@override State createState() => new SettingsState(helper, app, returnToScanner);
}
class MenuItem {
// Заголовок пункта меню и выбранное значение.
String title, selectedValue;
MenuItem(this.title, this.selectedValue);
}
class SettingsState extends BaseState<SettingsScreen> {
List<MenuItem> menuItems = [
new MenuItem('', ''),
new MenuItem('', '')
];
bool returnToScanner;
SettingsState(SqliteHelper helper, String app, bool returnToScanner) {
this.helper = helper;
this.app = app;
this.returnToScanner = returnToScanner;
}
@override Widget build(BuildContext ctx) {
helper.getSettings().then((info) {
setState(() {
menuItems[0].title = StringsLocalization.currency();
menuItems[1].title = StringsLocalization.locale();
menuItems[0].selectedValue = info['currency'].toString();
menuItems[1].selectedValue = info['locale'] == null ? Intl.defaultLocale : info['locale'].toString();
});
});
return new WillPopScope(onWillPop: onWillPop, child: getMainWidget());
}
Widget getMainWidget() {
return new Scaffold(appBar: getAppBar(),
body: getScreenContent());
}
@override
Widget getScreenContent() {
return new Container(
margin: new EdgeInsets.only(top: 16.0),
child: new ListView(children: getSettings()));
}
@override
List<Widget> getMenuButtons() {
return null;
}
List<Widget> getSettings() {
List<Widget> widgets = new List();
for (int i = 0; i < menuItems.length; i++) {
if (menuItems[i].selectedValue != '') {
widgets.add(getSettingsItem(() => onPressed(menuItems.indexOf(menuItems[i])),
menuItems[i].title,
i == 0 ? getCurrencyTitle(int.parse(menuItems[i].selectedValue)) : getLocaleTitle(menuItems[i].selectedValue)));
}
}
return widgets;
}
Widget getSettingsItem(VoidCallback onPressed, String title, String value) {
return new Container(
height: 56.0,
padding: new EdgeInsets.only(left: 8.0),
child: (new FlatButton(
onPressed: onPressed,
child: new Row(children: <Widget>[
new Expanded(child: new Text(title, style: new TextStyle(
fontWeight: FontWeight.w600,
color: faqGrey,
fontSize: 14.0))),
new Text(value,
style: new TextStyle(
fontWeight: FontWeight.w400,
color: faqGrey,
fontSize: 14.0)),
getArrow()
]))));
}
void onPressed(int position) {
switch (position) {
case 0 :
return pushRoute(context, new CurrenciesScreen(helper, app));
case 1 :
return pushRoute(context, new LanguagesScreen(helper, app));
}
}
Widget getArrow() {
return new Container(margin: new EdgeInsets.only(left: 8.0),
child: new Image.asset(settings_arrow_png, height: 42.0));
}
@override
String getTitle() {
return StringsLocalization.settings();
}
onWillPop() {
if(returnToScanner) {
return startScanner(context, app, helper);
} else {
return true;
}
}
}

220
lib/screens/splash.dart Normal file
View File

@@ -0,0 +1,220 @@
import 'dart:async';
import 'dart:convert';
import 'package:checker/base/base_state.dart';
import 'package:checker/common.dart';
import 'package:checker/consts.dart';
import 'package:checker/db.dart';
import 'package:checker/network.dart';
import 'package:checker/resources.dart';
import 'package:checker/screens/finish_registration.dart';
import 'package:checker/screens/registration.dart';
import 'package:checker/strings.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
class SplashScreen extends StatefulWidget {
@override State createState() => new _SplashScreenState();
}
class _SplashScreenState extends BaseState<SplashScreen> {
@override Widget build(BuildContext ctx) {
if (helper == null) {
helper = new SqliteHelper();
helper.open().then((_) {
if (app == null) {
platform.invokeMethod('getFlavor').then((flavor) {
app = flavor;
setState(() {
onStart();
});
});
}
});
}
return getScreenContent();
}
void onStart() {
helper.getLocale().then((locale) {
if (locale == null) {
initWithSystemValue();
} else {
initWithSavedValue();
}
});
}
void initWithSystemValue() {
platform.invokeMethod('getLocale').then((locale) {
helper.getSettings().then((settings) {
if (settings == null) {
createSettingsTable(locale);
} else {
initLocale(locale, () {
showNext();
});
}
});
});
}
void initWithSavedValue() {
helper.getLocale().then((locale) {
initLocale(locale, () {
showNext();
});
});
}
void createSettingsTable(String locale) {
platform.invokeMethod('getCurrency').then((currency) {
helper.createAppInfo(locale, currency);
initLocale(locale, () {
showNext();
});
});
}
void initLocale<T>(String locale, Future<T> onValue()) {
Intl.defaultLocale = locale;
StringsLocalization.load(locale).then((_) {
onValue();
});
}
void showNext() {
new Future.delayed(const Duration(milliseconds: 1000), () {
showNextScreen();
});
}
@override
Widget getScreenContent() {
return app == null
? getBackground()
: new Stack(
children: <Widget>[
getBackground(),
getLogo(),
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)))
]);
}
/// Возвращает столбец с логотипом приложения и текстом под ним.
/// Столбец занимает не все доступное пространство, а необходимый минимум в центре экрана.
getLogo() {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Image.asset(
Resources.getLogo(app),
height: 112.0,
width: 252.0),
new Image.asset(
splash_text_png,
height: 40.0,
width: 240.0)
]));
}
/// Запуск следующего экрана приложения.
showNextScreen() async {
String token = await helper.getToken();
String locale = await helper.getLocale();
// В случае, если в приложении отсутствует токен,
// необходимо запустить регистрацию кассы.
if (token == null) {
pushRouteReplacement(context, new RegistrationScreen(helper, app));
} else {
if (await platform.invokeMethod('isOnline')) {
checkTokenStatus(token, locale).then((statusResponse) {
handleStatusResponse(statusResponse, helper);
}).catchError((error) {
handleError(error.toString());
});
}
}
}
/// Обработка ответа.
/// В случае, если токен был удален может прийти active: false, либо 404.
/// Если токен не активен, попробовать создать его еще раз.
handleStatusResponse(var statusResponse, SqliteHelper helper) async {
int code = statusResponse.statusCode;
if (code == 404) {
helper.clear().then((result) {
pushRouteReplacement(context, new RegistrationScreen(helper, app));
});
} else {
Map status = JSON.decode(statusResponse.body);
bool active = status['active'] == null ? false : status['active'];
if (active) {
startScanner(context, app, helper);
} else {
if (await platform.invokeMethod('isOnline')) {
_createToken(helper);
}
}
}
}
/// Отправляется запрос на создание токена.
///
/// Если вернулся код 409, значит такой токен уже существует и активирован.
/// Нужно направить пользователя на экран подтверждения активации.
///
/// Если вернулся код 200, значит токен был ранее удален и только что снова создался.
/// Нужно удалить его и направить пользователя на экран регистрации.
_createToken(SqliteHelper helper) async {
String merchantID = await helper.getMerchantID();
String posID = await helper.getPosID();
String locale = await helper.getLocale();
createToken(merchantID, posID, locale).then((response) {
if (response.statusCode == 409) {
pushRouteReplacement(context, new FinishRegistrationScreen(helper, app));
} else if (response.statusCode == 201) {
clearToken(response, helper);
}
}).catchError((error) {
handleError(error.toString());
});
}
/// Очищаем бд, делаем запрос на удаление токена.
Future clearToken(Response response, SqliteHelper helper) async {
String locale = await helper.getLocale();
helper.clear().then((_) {
Map parsedMap = JSON.decode(response.body);
deleteToken(parsedMap['token'], locale).then((_) {
Navigator.of(context).pop();
pushRouteReplacement(context, new RegistrationScreen(helper, app));
}).catchError((error) {
handleError(error.toString());
});
});
}
/// Закрываем соединение, логируем ошибку.
void handleError(String error) {
helper.close().then((_) {
print(error.toString());
return false;
});
}
}

View File

@@ -1,145 +0,0 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:convert';
import 'dart:async';
import 'common.dart';
import 'network.dart';
import 'consts.dart';
import 'registration.dart';
import 'finish_registration.dart';
import 'resources.dart';
import 'base_state.dart';
class SplashScreen extends StatefulWidget {
@override State createState() => new _SplashScreenState();
}
class _SplashScreenState extends BaseState<SplashScreen> {
@override
Widget getScreenContent() {
return app == null
? getBackground()
: new Stack(
children: <Widget>[
getBackground(),
getLogo(),
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)))]);
}
@override Widget getMainWidget() {
return getScreenContent();
}
@override
String getTitle() {
return null;
}
@override void onStart() {
new Future.delayed(const Duration(milliseconds: 1000), () {
showNextScreen(context);
});
}
/// Возвращает столбец с логотипом приложения и текстом под ним.
/// Столбец занимает не все доступное пространство, а необходимый минимум в центре экрана.
getLogo() {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Image.asset(
Resources.getLogo(app),
height: 112.0,
width: 252.0),
new Image.asset(
splash_text_png,
height: 40.0,
width: 240.0)]));
}
/// Запуск следующего экрана приложения.
showNextScreen(BuildContext context) async {
String token = await platform.invokeMethod('getToken');
// В случае, если в приложении отсутствует токен,
// необходимо запустить регистрацию кассы.
if (token == null) {
pushRoute(context, new RegistrationScreen());
} else {
if (await platform.invokeMethod('isOnline')) {
checkTokenStatus(token).then((statusResponse) {
handleStatusResponse(context, statusResponse);
}).catchError((error) {
print(error.toString());
return false;
});
}
}
}
/// Обработка ответа.
/// В случае, если токен был удален может прийти active: false, либо 404.
/// Если токен не активен, попробовать создать его еще раз.
handleStatusResponse(BuildContext context, var statusResponse) async {
int code = statusResponse.statusCode;
print('resp: ${code}');
if (code == 404) {
platform.invokeMethod('removeKeys').then((result) {
print('try to start registration');
pushRoute(context, new RegistrationScreen());
});
} else {
Map status = JSON.decode(statusResponse.body);
bool active = status['active'] == null ? false : status['active'];
if (active) {
startScanner(context, app);
} else {
if (await platform.invokeMethod('isOnline')) {
_createToken(context);
}
}
}
}
/// Отправляется запрос на создание токена.
///
/// Если вернулся код 409, значит такой токен уже существует и активирован.
/// Нужно направить пользователя на экран подтверждения активации.
///
/// Если вернулся код 200, значит токен был ранее удален и только что снова создался.
/// Нужно удалить его и направить пользователя на экран регистрации.
_createToken(BuildContext ctx) async {
String merchantID = await platform.invokeMethod('getMerchantID');
String posID = await platform.invokeMethod('getPosID');
createToken(merchantID, posID).then((response) {
if (response.statusCode == 409) {
pushRoute(ctx, new FinishRegistrationScreen());
} else if (response.statusCode == 201) {
platform.invokeMethod('removeKeys').then((result) {
Map parsedMap = JSON.decode(result);
deleteToken(parsedMap['token']).then((response) {
print(response.body);
Navigator.of(ctx).pop(); // Убираем текущий route
pushRoute(ctx, new RegistrationScreen()); // Запускаем регистрацию
}).catchError((error) {
print(error.toString());
});
});
}
}).catchError((error) => print(error.toString()));
}
}

View File

@@ -9,15 +9,78 @@ class StringsLocalization {
return initializeMessages(locale); return initializeMessages(locale);
} }
static String confirmPurchase(String val) { static String declineCurrency(int num, int code) {
return sprintf(Intl.message('confirm_purchase', name: 'confirm_purchase', locale: Intl.defaultLocale), [val]);
int residual = num % 100;
if (residual >= 20) {
residual %= 10;
} }
static String purchaseCompleted(String val) { List<String> strings = currencies(code);
return sprintf(Intl.message('purchase_complite', name: 'purchase_complite', locale: Intl.defaultLocale), [val]);
switch (residual) {
case 1:
return strings[0];
case 2:
case 3:
case 4:
return strings[1];
default: // case 0, 5-19
return strings[2];
}
}
static List<String> currencies(int code) {
String nominative, singular, plural;
switch (code) {
case 643:
nominative = nominativeRuble();
singular = singularRuble();
plural = pluralRuble();
break;
case 840:
nominative = nominativeDollar();
singular = singularDollar();
plural = pluralDollar();
break;
case 980:
nominative = nominativeHryvna();
singular = singularHryvna();
plural = pluralHryvna();
break;
case 978:
nominative = nominativeEuro();
singular = singularEuro();
plural = pluralEuro();
break;
case 398:
nominative = nominativeTenge();
singular = singularTenge();
plural = pluralTenge();
break;
}
return [nominative, singular, plural];
}
static String confirmPurchase(String val, int code) {
String trimmedVal =val.substring(0, val.length - 3);
return sprintf(Intl.message('confirm_purchase', name: 'confirm_purchase', locale: Intl.defaultLocale), [trimmedVal, declineCurrency(int.parse(trimmedVal), code)]);
}
static String purchaseCompleted(String val, int code) {
String trimmedVal =val.substring(0, val.length - 3);
return sprintf(Intl.message('purchase_complite', name: 'purchase_complite', locale: Intl.defaultLocale), [val, declineCurrency(int.parse(trimmedVal), code)]);
} }
static String registration() => Intl.message('registration', name: 'registration', locale: Intl.defaultLocale); static String registration() => Intl.message('registration', name: 'registration', locale: Intl.defaultLocale);
static String usage() => Intl.message('usage', name: 'usage', locale: Intl.defaultLocale);
static String support() => Intl.message('support', name: 'support', locale: Intl.defaultLocale);
static String common() => Intl.message('common', name: 'common', locale: Intl.defaultLocale);
static String idStore() => Intl.message('ID_Store', name: 'ID_Store', locale: Intl.defaultLocale); static String idStore() => Intl.message('ID_Store', name: 'ID_Store', locale: Intl.defaultLocale);
static String signUp() => Intl.message('sign_up', name: 'sign_up', locale: Intl.defaultLocale); static String signUp() => Intl.message('sign_up', name: 'sign_up', locale: Intl.defaultLocale);
static String specifyDinStore() => Intl.message('specify_din_store', name: 'specify_din_store', locale: Intl.defaultLocale); static String specifyDinStore() => Intl.message('specify_din_store', name: 'specify_din_store', locale: Intl.defaultLocale);
@@ -30,7 +93,6 @@ class StringsLocalization {
static String appActivated() => Intl.message('app_activ', name: 'app_activ', locale: Intl.defaultLocale); static String appActivated() => Intl.message('app_activ', name: 'app_activ', locale: Intl.defaultLocale);
static String completeRegistration() => Intl.message('complite_activ', name: 'complite_activ', locale: Intl.defaultLocale); static String completeRegistration() => Intl.message('complite_activ', name: 'complite_activ', locale: Intl.defaultLocale);
static String cardScanner() => Intl.message('card_scaner', name: 'card_scaner', locale: Intl.defaultLocale); static String cardScanner() => Intl.message('card_scaner', name: 'card_scaner', locale: Intl.defaultLocale);
static String userName() => Intl.message('user_name', name: 'user_name', locale: Intl.defaultLocale);
static String card() => Intl.message('card', name: 'card', locale: Intl.defaultLocale); static String card() => Intl.message('card', name: 'card', locale: Intl.defaultLocale);
static String reward() => Intl.message('reward', name: 'reward', locale: Intl.defaultLocale); static String reward() => Intl.message('reward', name: 'reward', locale: Intl.defaultLocale);
static String sum() => Intl.message('sum', name: 'sum', locale: Intl.defaultLocale); static String sum() => Intl.message('sum', name: 'sum', locale: Intl.defaultLocale);
@@ -39,4 +101,39 @@ class StringsLocalization {
static String scan() => Intl.message('scan', name: 'scan', locale: Intl.defaultLocale); static String scan() => Intl.message('scan', name: 'scan', locale: Intl.defaultLocale);
static String buyer() => Intl.message('buyer', name: 'buyer', locale: Intl.defaultLocale); static String buyer() => Intl.message('buyer', name: 'buyer', locale: Intl.defaultLocale);
static String idNotFound() => Intl.message('ID_not_found', name: 'ID_not_found', locale: Intl.defaultLocale); static String idNotFound() => Intl.message('ID_not_found', name: 'ID_not_found', locale: Intl.defaultLocale);
static String settings() => Intl.message('settings', name: 'settings', locale: Intl.defaultLocale);
static String help() => Intl.message('help', name: 'help', locale: Intl.defaultLocale);
static String logout() => Intl.message('logout', name: 'logout', locale: Intl.defaultLocale);
static String currency() => Intl.message('currency', name: 'currency', locale: Intl.defaultLocale);
static String locale() => Intl.message('locale', name: 'locale', locale: Intl.defaultLocale);
// Валюты
static String nominativeRuble() => Intl.message('nominative_ruble', name: 'nominative_ruble', locale: Intl.defaultLocale);
static String singularRuble() => Intl.message('singular_ruble', name: 'singular_ruble', locale: Intl.defaultLocale);
static String pluralRuble() => Intl.message('plural_ruble', name: 'plural_ruble', locale: Intl.defaultLocale);
static String nominativeEuro() => Intl.message('nominative_euro', name: 'nominative_euro', locale: Intl.defaultLocale);
static String singularEuro() => Intl.message('singular_euro', name: 'singular_euro', locale: Intl.defaultLocale);
static String pluralEuro() => Intl.message('plural_euro', name: 'plural_euro', locale: Intl.defaultLocale);
static String nominativeDollar() => Intl.message('nominative_dollar', name: 'nominative_dollar', locale: Intl.defaultLocale);
static String singularDollar() => Intl.message('singular_dollar', name: 'singular_dollar', locale: Intl.defaultLocale);
static String pluralDollar() => Intl.message('plural_dollar', name: 'plural_dollar', locale: Intl.defaultLocale);
static String nominativeHryvna() => Intl.message('nominative_hryvna', name: 'nominative_hryvna', locale: Intl.defaultLocale);
static String singularHryvna() => Intl.message('singular_hryvna', name: 'singular_hryvna', locale: Intl.defaultLocale);
static String pluralHryvna() => Intl.message('plural_hryvna', name: 'plural_hryvna', locale: Intl.defaultLocale);
static String nominativeTenge() => Intl.message('nominative_tenge', name: 'nominative_tenge', locale: Intl.defaultLocale);
static String singularTenge() => Intl.message('singular_tenge', name: 'singular_tenge', locale: Intl.defaultLocale);
static String pluralTenge() => Intl.message('plural_tenge', name: 'plural_tenge', locale: Intl.defaultLocale);
static String registrationGuide() => Intl.message('registration_guide', name: 'registration_guide', locale: Intl.defaultLocale);
static String usageGuide() => Intl.message('usage_guide', name: 'usage_guide', locale: Intl.defaultLocale);
static String commonGuide() => Intl.message('common_guide', name: 'common_guide', locale: Intl.defaultLocale);
static String supportGuide(String phone, String url) {
return sprintf(Intl.message('support_guide', name: 'support_guide', locale: Intl.defaultLocale), [phone, url]);
}
} }

View File

@@ -6,6 +6,8 @@ dependencies:
intl: '>=0.14.0 <0.16.0' intl: '>=0.14.0 <0.16.0'
intl_translation: '>=0.14.0 <0.16.0' intl_translation: '>=0.14.0 <0.16.0'
sprintf: "^3.0.2" sprintf: "^3.0.2"
path_provider: "^0.2.1+1"
sqflite: any
flutter: flutter:
sdk: flutter sdk: flutter
@@ -30,10 +32,14 @@ flutter:
- assets/autobonus_splash.png - assets/autobonus_splash.png
- assets/pip_splash.png - assets/pip_splash.png
- assets/settings.png
- assets/settings_arrow.png
- assets/help.png
- assets/check.png
- assets/logout.png - assets/logout.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
- assets/expansion_icon.png - assets/faq_expansion_icon.png
- assets/powered_by_dinect_splash.png - assets/powered_by_dinect_splash.png
- assets/powered_by_dinect.png - assets/powered_by_dinect.png
- assets/autobonus_logo.png - assets/autobonus_logo.png