diff --git a/android/app/build.gradle b/android/app/build.gradle index 7bef710..6ddf713 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,36 +46,42 @@ android { applicationId 'com.dinect.autobonus' buildConfigField "String", "locale", "\"en\"" buildConfigField "String", "flavor", "\"autobonus\"" + buildConfigField "int", "currency", "643" } autobonus_ru { applicationId 'com.dinect.autobonus' buildConfigField "String", "locale", "\"ru\"" buildConfigField "String", "flavor", "\"autobonus\"" + buildConfigField "int", "currency", "643" } autobonus_ua { applicationId 'com.dinect.autobonus' buildConfigField "String", "locale", "\"ua\"" buildConfigField "String", "flavor", "\"autobonus\"" + buildConfigField "int", "currency", "643" } pip_en { applicationId 'com.dinect.pip' buildConfigField "String", "locale", "\"en\"" buildConfigField "String", "flavor", "\"pip\"" + buildConfigField "int", "currency", "980" } pip_ru { applicationId 'com.dinect.pip' buildConfigField "String", "locale", "\"ru\"" buildConfigField "String", "flavor", "\"pip\"" + buildConfigField "int", "currency", "980" } pip_ua { applicationId 'com.dinect.pip' buildConfigField "String", "locale", "\"ua\"" buildConfigField "String", "flavor", "\"pip\"" + buildConfigField "int", "currency", "980" } } diff --git a/android/app/src/main/java/com/dinect/checker/MainActivity.java b/android/app/src/main/java/com/dinect/checker/MainActivity.java index 80a56c9..9640d79 100644 --- a/android/app/src/main/java/com/dinect/checker/MainActivity.java +++ b/android/app/src/main/java/com/dinect/checker/MainActivity.java @@ -66,6 +66,10 @@ public class MainActivity extends FlutterActivity { result.success(BuildConfig.flavor); break; + case "getCurrency": + result.success(BuildConfig.currency); + break; + case "startScanner": final Map arguments = call.arguments(); final int idx = mPreferences.getInt(SCANNER_BACKEND_KEY, 0); @@ -144,10 +148,13 @@ public class MainActivity extends FlutterActivity { } - public void getLocale() { + public void getCurrency() { } + public void getLocale() { + + } public void startScanner() { diff --git a/assets/settings_arrow.png b/assets/settings_arrow.png new file mode 100644 index 0000000..7b3efe8 Binary files /dev/null and b/assets/settings_arrow.png differ diff --git a/lib/base_state.dart b/lib/base_state.dart index 4aa805c..13b854a 100644 --- a/lib/base_state.dart +++ b/lib/base_state.dart @@ -3,8 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; -import 'common.dart'; -import 'consts.dart'; +import 'package:checker/common.dart'; +import 'package:checker/consts.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 extends State { @@ -21,24 +24,23 @@ abstract class BaseState extends State { 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(() { + if (helper == null) { + helper = new SqliteHelper(); + helper.open().then((_) { + if (app == null) { + platform.invokeMethod('getFlavor').then((flavor) { app = flavor; - helper = new SqliteHelper(); - helper.open().then((_){ + setState(() { onStart(); }); }); - }); - } - }); + } + }); + } return getMainWidget(); } @@ -67,7 +69,9 @@ abstract class BaseState extends State { Widget getScreenContent(); /// Возвращает заголовок для AppBar - String getTitle(); + String getTitle() { + return null; + } AppBar getAppBar() { return new AppBar(title: new Text(getTitle(), style: new TextStyle(fontSize: 18.0)), @@ -76,29 +80,44 @@ abstract class BaseState extends State { List getMenuButtons() { return [ - new PopupMenuButton( + new PopupMenuButton( + onSelected: onOptionsItemClick, itemBuilder: (BuildContext context) { - [ - new PopupMenuItem( - child: new Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - new Image.asset( - settings_png, width: 48.0, height: 48.0), - new Image.asset(help_png, width: 48.0, height: 48.0), - new Image.asset(logout_png, width: 48.0, height: 48.0), - ])) + 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() { - return new IconButton(icon: new Icon(Icons.help_outline), onPressed: () => faq(context, false)); + void onOptionsItemClick(int index) { + switch (index) { + case 0: { + pushRoute(context, new SettingsScreen()); + break; + } + case 1: { + pushRoute(context, new FAQScreen(false)); + break; + } + case 0: { + logout(context); + } + } } - 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 с подсказкой. @@ -112,7 +131,7 @@ abstract class BaseState extends State { /// Возвращает подсказку, либо ошибку, если введенные в поле ввода данные неверны. String getHintString() { - if (textFieldValue.length == 0 && error == null) { + if (dinCode.length == 0 && error == null) { return ' '; } else if (error != null) { return error; @@ -130,7 +149,7 @@ abstract class BaseState extends State { /// Смена состояния экрана при изменении текста в поле ввода. void handleUserInput(String text) { setState(() { - textFieldValue = text; + dinCode = text; }); } diff --git a/lib/consts.dart b/lib/consts.dart index 675775e..313b78c 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -3,13 +3,14 @@ import 'package:flutter/material.dart'; // Serious constants const String appName = "AutoBonus"; -const String url = 'https://pos-api-autoclub.dinect.com/20130701/'; -const String appToken = 'bdea0f3ba9034b688019a7cac753d1209e2b227f'; +const String url = 'https://pos-api-int.dinect.com/20130701/'; +const String appToken = '9fec83cdca38c357e6b65dbb17514cdd36bf2a08'; // Assets 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 activate_token_bg_png = 'assets/activate_token_message_background.png'; const String active_token_bg_png = 'assets/active_token_message_background.png'; diff --git a/lib/db.dart b/lib/db.dart index db078d3..cb864d1 100644 --- a/lib/db.dart +++ b/lib/db.dart @@ -5,12 +5,20 @@ 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, @@ -28,15 +36,22 @@ class SqliteHelper { String path = join(documentsDirectory.path, "demo.db"); db = await openDatabase(path, version: 1, onCreate: (Database db, int version) async { - await db.execute('''create table session ( + + 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 insert(String merchantID, String posID, String token) async { + /// Создается запись в таблице, содержащая + /// необходимые для идентификации пользователя и проведения запросов. + Future createSession(String merchantID, String posID, String token) async { Map session = { columnMerchantID: merchantID, @@ -48,36 +63,53 @@ class SqliteHelper { return db.insert(tableSession, session); } + /// Создается запись в таблице, содержащая данные, которые не зависят от сессии. + Future createAppInfo(String locale, int currency) async { + List appInfo = await db.query(tableSettings); + if (appInfo.length > 0) { + return null; + } else { + return db.insert(tableSettings, { + columnLocale: locale, + columnCurrency: currency + }); + } + } + + Future getSettings() async { + return await selectAll(tableSettings); + } + Future getToken() async { - Map session = await _getSession(); + Map session = await selectAll(tableSession); String token = session != null ? session[columnToken] : null; print('token: {$token}'); return token; } Future getMerchantID() async { - Map session = await _getSession(); + Map session = await selectAll(tableSession); String merchantID = session != null ? session[columnMerchantID] : null; print('token: {$merchantID}'); return merchantID; } Future getPosID() async { - Map session = await _getSession(); + Map session = await selectAll(tableSession); return session != null ? session[columnPosID] : new DateTime.now().millisecondsSinceEpoch.toString(); } Future getDocID() async { - Map session = await _getSession(); + Map session = await selectAll(tableSession); int docID = session != null ? session[columnDocID] : 0; db.update(tableSession, {columnDocID: docID + 1}); print('docid: {$docID}'); return docID; } - Future _getSession() async { + Future selectAll(String table) async { - List maps = await db.query(tableSession, columns: null); + List maps = await db.query(table, columns: null); if (maps.length > 0) { return maps.first; diff --git a/lib/i18n/messages_en.dart b/lib/i18n/messages_en.dart index 846e453..c5a0503 100644 --- a/lib/i18n/messages_en.dart +++ b/lib/i18n/messages_en.dart @@ -38,6 +38,11 @@ class MessageLookup extends MessageLookupByLibrary { "sum" : MessageLookupByLibrary.simpleMessage("Sum"), "update_activ_status" : MessageLookupByLibrary.simpleMessage("Update activation status"), "user_name" : MessageLookupByLibrary.simpleMessage("User name"), - "yes" : MessageLookupByLibrary.simpleMessage("Yes") + "yes" : MessageLookupByLibrary.simpleMessage("Yes"), + "settings" : MessageLookupByLibrary.simpleMessage("Settings"), + "help " : MessageLookupByLibrary.simpleMessage("FAQ"), + "logout" : MessageLookupByLibrary.simpleMessage("Logout"), + "currency" : MessageLookupByLibrary.simpleMessage("Currency"), + "locale" : MessageLookupByLibrary.simpleMessage("Locale") }; } diff --git a/lib/i18n/messages_ru.dart b/lib/i18n/messages_ru.dart index bce5b66..0b2733a 100644 --- a/lib/i18n/messages_ru.dart +++ b/lib/i18n/messages_ru.dart @@ -38,6 +38,11 @@ class MessageLookup extends MessageLookupByLibrary { "sum" : MessageLookupByLibrary.simpleMessage("Сумма"), "update_activ_status" : 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("Язык") }; } diff --git a/lib/screens/faq.dart b/lib/screens/faq.dart index 481f02c..b6457b6 100644 --- a/lib/screens/faq.dart +++ b/lib/screens/faq.dart @@ -51,10 +51,6 @@ class FAQScreenState extends BaseState { return "FAQ"; } - @override getMenuButtons() { - return [getLogoutButton()]; - } - @override String getHint() { return null; } diff --git a/lib/screens/purchase.dart b/lib/screens/purchase.dart index 88e60b2..dd2d915 100644 --- a/lib/screens/purchase.dart +++ b/lib/screens/purchase.dart @@ -86,10 +86,6 @@ class PurchaseScreenState extends BaseState { return StringsLocalization.sum(); } - @override getMenuButtons() { - return [getFaqButton(), getLogoutButton()]; - } - @override getTextWidget() { return new TextField( keyboardType: TextInputType.number, diff --git a/lib/screens/purchase_success.dart b/lib/screens/purchase_success.dart index fc0134b..40ea706 100644 --- a/lib/screens/purchase_success.dart +++ b/lib/screens/purchase_success.dart @@ -22,10 +22,6 @@ class PurchaseSuccessScreenState extends BaseState { String sum; String username; - @override getMenuButtons() { - return [getFaqButton(), getLogoutButton()]; - } - @override String getTitle() { return StringsLocalization.carryingPurchase(); } diff --git a/lib/screens/registration.dart b/lib/screens/registration.dart index ff61741..3b56c09 100644 --- a/lib/screens/registration.dart +++ b/lib/screens/registration.dart @@ -1,4 +1,3 @@ -import 'package:checker/db.dart'; import 'package:checker/screens/finish_registration.dart'; import 'package:flutter/material.dart'; import 'dart:convert'; // Пакет для обработки json с ответом от сервера. @@ -56,8 +55,8 @@ class _RegistrationScreenState extends BaseState { /// Токен кассы - это DIN код. DIN код - это специальный код динекта, максимальная его длина - 25 символов. _isValidMerchantID() { - print("${textFieldValue.length}"); - return textFieldValue.length > 0 && textFieldValue.length < 25; + print("${dinCode.length}"); + return dinCode.length > 0 && dinCode.length < 25; } /// Показать progressBar, запросить токен. @@ -74,7 +73,7 @@ class _RegistrationScreenState extends BaseState { String posID = await helper.getPosID(); - createToken(textFieldValue, posID).then((response) { + createToken(dinCode, posID).then((response) { setState(() { error = null; @@ -86,7 +85,7 @@ class _RegistrationScreenState extends BaseState { Map parsedMap = JSON.decode(response.body); if (response.statusCode == 201) { - helper.insert(textFieldValue, posID, parsedMap['token']).then((_) { + helper.createSession(dinCode, posID, parsedMap['token']).then((_) { helper.close(); pushRoute(context, new FinishRegistrationScreen()); }); diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart new file mode 100644 index 0000000..a7f1a43 --- /dev/null +++ b/lib/screens/settings.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'dart:convert'; +import 'dart:async'; +import 'package:checker/common.dart'; +import 'package:checker/network.dart'; +import 'package:checker/consts.dart'; +import 'package:checker/db.dart'; +import 'package:checker/strings.dart'; +import 'package:checker/base_state.dart'; +import 'package:checker/screens/registration.dart'; +import 'package:checker/screens/finish_registration.dart'; + +class SettingsScreen extends StatefulWidget { + @override State createState() => new _SettingsState(); +} + +class MenuItem { + + MenuItem(); + + String title; + String selectedValue; +} + +class _SettingsState extends BaseState { + + List menuItems; + + @override onStart() { + if (menuItems == null) { + helper.getSettings().then((info) { + setState(() { + + print("load settings"); + + menuItems = [new MenuItem(), new MenuItem()]; + + menuItems[0].title = StringsLocalization.locale(); + menuItems[0].selectedValue = info["locale"]; + + menuItems[1].title = StringsLocalization.currency(); + menuItems[1].selectedValue = info["currency"].toString(); + }); + }); + } + } + + @override + Widget getScreenContent() { + return menuItems == null + ? getBackground() + : new ListView(children: getSettings()); + } + + List getSettings() { + List widgets = new List(); + for (MenuItem item in menuItems) { + widgets.add(new Row(children: [ + new Text(item.title, textAlign: TextAlign.left), + new Text(item.selectedValue,textAlign: TextAlign.right), + new Image.asset(settings_arrow_png, width: 28.0, height: 28.0, alignment: FractionalOffset.centerRight)])); + } + return widgets; + } + + @override + String getTitle() { + return StringsLocalization.settings(); + } +} \ No newline at end of file diff --git a/lib/screens/splash.dart b/lib/screens/splash.dart index 0585269..28eab62 100644 --- a/lib/screens/splash.dart +++ b/lib/screens/splash.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart'; +import 'package:intl/intl.dart'; import 'dart:convert'; import 'dart:async'; import 'package:checker/common.dart'; @@ -17,6 +18,33 @@ class SplashScreen extends StatefulWidget { class _SplashScreenState extends BaseState { + @override Widget getMainWidget() { + return getScreenContent(); + } + + @override void onStart() { + helper.getSettings().then((info) { + if (info == null) { + platform.invokeMethod('getLocale').then((locale) { + Intl.defaultLocale = locale; + platform.invokeMethod('getCurrency').then((currency) { + helper.createAppInfo(locale, currency).then((_) { + showNext(); + }); + }); + }); + } else { + showNext(); + } + }); + } + + void showNext() { + new Future.delayed(const Duration(milliseconds: 1000), () { + showNextScreen(); + }); + } + @override Widget getScreenContent() { return app == null @@ -36,21 +64,6 @@ class _SplashScreenState extends BaseState { 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() { @@ -69,10 +82,10 @@ class _SplashScreenState extends BaseState { } /// Запуск следующего экрана приложения. - showNextScreen(BuildContext context) async { + showNextScreen() async { String token = await helper.getToken(); - + // В случае, если в приложении отсутствует токен, // необходимо запустить регистрацию кассы. if (token == null) { @@ -81,7 +94,7 @@ class _SplashScreenState extends BaseState { } else { if (await platform.invokeMethod('isOnline')) { checkTokenStatus(token).then((statusResponse) { - handleStatusResponse(context, statusResponse, helper); + handleStatusResponse(statusResponse, helper); }).catchError((error) { helper.close().then((_) { print(error.toString()); @@ -95,7 +108,7 @@ class _SplashScreenState extends BaseState { /// Обработка ответа. /// В случае, если токен был удален может прийти active: false, либо 404. /// Если токен не активен, попробовать создать его еще раз. - handleStatusResponse(BuildContext context, var statusResponse, SqliteHelper helper) async { + handleStatusResponse(var statusResponse, SqliteHelper helper) async { int code = statusResponse.statusCode; print('resp: ${code}'); @@ -114,7 +127,7 @@ class _SplashScreenState extends BaseState { startScanner(context, app, helper); } else { if (await platform.invokeMethod('isOnline')) { - _createToken(context, helper); + _createToken(helper); } } } @@ -127,7 +140,7 @@ class _SplashScreenState extends BaseState { /// /// Если вернулся код 200, значит токен был ранее удален и только что снова создался. /// Нужно удалить его и направить пользователя на экран регистрации. - _createToken(BuildContext ctx, SqliteHelper helper) async { + _createToken(SqliteHelper helper) async { String merchantID = await helper.getMerchantID(); String posID = await helper.getPosID(); @@ -135,22 +148,22 @@ class _SplashScreenState extends BaseState { createToken(merchantID, posID).then((response) { if (response.statusCode == 409) { helper.close(); - pushRoute(ctx, new FinishRegistrationScreen()); + pushRoute(context, new FinishRegistrationScreen()); } else if (response.statusCode == 201) { - clearToken(response, ctx, helper); + clearToken(response, helper); } }).catchError((error) => print(error.toString())); } /// Очищаем бд, делаем запрос на удаление токена. - void clearToken(Response response, BuildContext ctx, SqliteHelper helper) { + void clearToken(Response response, SqliteHelper helper) { helper.clear().then((_) { Map parsedMap = JSON.decode(response.body); deleteToken(parsedMap['token']).then((_) { helper.close(); - Navigator.of(ctx).pop(); // Убираем текущий route - pushRoute(ctx, new RegistrationScreen()); // Запускаем регистрацию + Navigator.of(context).pop(); // Убираем текущий route + pushRoute(context, new RegistrationScreen()); // Запускаем регистрацию }).catchError((error) { helper.close(); print(error.toString()); diff --git a/lib/strings.dart b/lib/strings.dart index 8b030f7..0221352 100644 --- a/lib/strings.dart +++ b/lib/strings.dart @@ -39,4 +39,9 @@ class StringsLocalization { static String scan() => Intl.message('scan', name: 'scan', 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 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); } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index dc47938..6a5929c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ flutter: - assets/pip_splash.png - assets/settings.png + - assets/settings_arrow.png - assets/help.png - assets/logout.png - assets/activate_token_message_background.png