В бд добавлена таблица настроек, данные для настроек берутся из базы

This commit is contained in:
Ivan Murashov
2017-09-08 17:25:56 +03:00
parent 29f6019caf
commit 0dc8ab5da0
16 changed files with 239 additions and 87 deletions

View File

@@ -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<T extends StatefulWidget> extends State<T> {
@@ -21,24 +24,23 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
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<T extends StatefulWidget> extends State<T> {
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<T extends StatefulWidget> extends State<T> {
List<Widget> getMenuButtons() {
return <Widget>[
new PopupMenuButton(
new PopupMenuButton<int>(
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<T extends StatefulWidget> extends State<T> {
/// Возвращает подсказку, либо ошибку, если введенные в поле ввода данные неверны.
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<T extends StatefulWidget> extends State<T> {
/// Смена состояния экрана при изменении текста в поле ввода.
void handleUserInput(String text) {
setState(() {
textFieldValue = text;
dinCode = text;
});
}

View File

@@ -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';

View File

@@ -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<Map> appInfo = await db.query(tableSettings);
if (appInfo.length > 0) {
return null;
} else {
return db.insert(tableSettings, {
columnLocale: locale,
columnCurrency: currency
});
}
}
Future<Map> getSettings() async {
return await selectAll(tableSettings);
}
Future<String> getToken() async {
Map session = await _getSession();
Map session = await selectAll(tableSession);
String token = session != null ? session[columnToken] : null;
print('token: {$token}');
return token;
}
Future<String> getMerchantID() async {
Map session = await _getSession();
Map session = await selectAll(tableSession);
String merchantID = session != null ? session[columnMerchantID] : null;
print('token: {$merchantID}');
return merchantID;
}
Future<String> getPosID() async {
Map session = await _getSession();
Map session = await selectAll(tableSession);
return session != null ? session[columnPosID] : new DateTime.now().millisecondsSinceEpoch.toString();
}
Future<int> 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<Map> _getSession() async {
Future<Map> selectAll(String table) async {
List<Map> maps = await db.query(tableSession, columns: null);
List<Map> maps = await db.query(table, columns: null);
if (maps.length > 0) {
return maps.first;

View File

@@ -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")
};
}

View File

@@ -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("Язык")
};
}

View File

@@ -51,10 +51,6 @@ class FAQScreenState<T> extends BaseState<FAQScreen> {
return "FAQ";
}
@override getMenuButtons() {
return <Widget>[getLogoutButton()];
}
@override String getHint() {
return null;
}

View File

@@ -86,10 +86,6 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
return StringsLocalization.sum();
}
@override getMenuButtons() {
return <Widget>[getFaqButton(), getLogoutButton()];
}
@override getTextWidget() {
return new TextField(
keyboardType: TextInputType.number,

View File

@@ -22,10 +22,6 @@ class PurchaseSuccessScreenState<T> extends BaseState<PurchaseSuccessScreen> {
String sum;
String username;
@override getMenuButtons() {
return <Widget>[getFaqButton(), getLogoutButton()];
}
@override String getTitle() {
return StringsLocalization.carryingPurchase();
}

View File

@@ -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<RegistrationScreen> {
/// Токен кассы - это 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<RegistrationScreen> {
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<RegistrationScreen> {
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());
});

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

@@ -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<SettingsScreen> {
List<MenuItem> 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<Widget> getSettings() {
List<Widget> 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();
}
}

View File

@@ -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<SplashScreen> {
@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<SplashScreen> {
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<SplashScreen> {
}
/// Запуск следующего экрана приложения.
showNextScreen(BuildContext context) async {
showNextScreen() async {
String token = await helper.getToken();
// В случае, если в приложении отсутствует токен,
// необходимо запустить регистрацию кассы.
if (token == null) {
@@ -81,7 +94,7 @@ class _SplashScreenState extends BaseState<SplashScreen> {
} 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<SplashScreen> {
/// Обработка ответа.
/// В случае, если токен был удален может прийти 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<SplashScreen> {
startScanner(context, app, helper);
} else {
if (await platform.invokeMethod('isOnline')) {
_createToken(context, helper);
_createToken(helper);
}
}
}
@@ -127,7 +140,7 @@ class _SplashScreenState extends BaseState<SplashScreen> {
///
/// Если вернулся код 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<SplashScreen> {
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());

View File

@@ -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);
}