pin release

fix_ssl_master
suvaissov 2021-01-19 01:01:58 +06:00
parent 923e32274a
commit 839432d011
16 changed files with 358 additions and 12 deletions

BIN
assets/images/finger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/images/secBg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -4,5 +4,6 @@ const String ImageShowRoute = "ImageShowRoute";
const String PaymentViewRoute = "PaymentView"; const String PaymentViewRoute = "PaymentView";
const String HistoryViewRoute = "HistoryView"; const String HistoryViewRoute = "HistoryView";
const String InfoKkmViewRoute = "InfoKkmViewRoute"; const String InfoKkmViewRoute = "InfoKkmViewRoute";
const String SettingsViewRoute = "SettingsViewRoute";
const String QrViewRoute = "QrViewRoute"; const String QrViewRoute = "QrViewRoute";
// Generate the views here // Generate the views here

View File

@ -3,6 +3,7 @@ import 'package:aman_kassa_flutter/views/history/history_view.dart';
import 'package:aman_kassa_flutter/views/info_kkm/info_kkm_view.dart'; import 'package:aman_kassa_flutter/views/info_kkm/info_kkm_view.dart';
import 'package:aman_kassa_flutter/views/payment/payment_view.dart'; import 'package:aman_kassa_flutter/views/payment/payment_view.dart';
import 'package:aman_kassa_flutter/views/qr_view/qr_view.dart'; import 'package:aman_kassa_flutter/views/qr_view/qr_view.dart';
import 'package:aman_kassa_flutter/views/settings/settings_view.dart';
import './route_names.dart'; import './route_names.dart';
import 'package:aman_kassa_flutter/views/home/home_view.dart'; import 'package:aman_kassa_flutter/views/home/home_view.dart';
@ -38,6 +39,11 @@ Route<dynamic> generateRoute(RouteSettings settings) {
routeName: settings.name, routeName: settings.name,
viewToShow: InfoKkmView(), viewToShow: InfoKkmView(),
); );
case SettingsViewRoute:
return _getPageRoute(
routeName: settings.name,
viewToShow: SettingView(),
);
case QrViewRoute: case QrViewRoute:
ImageShowModel data = settings.arguments as ImageShowModel; ImageShowModel data = settings.arguments as ImageShowModel;
return _getPageRoute( return _getPageRoute(

View File

@ -23,4 +23,22 @@ ThunkAction<AppState> changeTradeTypeFromSetting(String tradeType) {
return (Store<AppState> store) async { return (Store<AppState> store) async {
store.dispatch(SetSettingStateAction(SettingState(tradeType: tradeType ))); store.dispatch(SetSettingStateAction(SettingState(tradeType: tradeType )));
}; };
}
ThunkAction<AppState> changePinCodeFromSetting(String pinCode) {
return (Store<AppState> store) async {
store.dispatch(SetSettingStateAction(SettingState(pinCode: pinCode)));
};
}
ThunkAction<AppState> changePinLockedFromSetting(bool locked) {
return (Store<AppState> store) async {
store.dispatch(SetSettingStateAction(SettingState(pinLocked: locked)));
};
}
ThunkAction<AppState> changePinSkipFromSetting(bool skip) {
return (Store<AppState> store) async {
store.dispatch(SetSettingStateAction(SettingState(pinSkip: skip)));
};
} }

View File

@ -72,7 +72,7 @@ Future<void> logoutAction(Store<AppState> store) async {
UserState( UserState(
isLoading: false, isLoading: false,
isAuthenticated: false, isAuthenticated: false,
user: User(), user: User()
), ),
), ),
); );

View File

@ -5,6 +5,9 @@ settingReducer(SettingState prevState, SetSettingStateAction action) {
final payload = action.settingState; final payload = action.settingState;
return prevState.copyWith( return prevState.copyWith(
mode: payload.mode, mode: payload.mode,
tradeType: payload.tradeType tradeType: payload.tradeType,
pinCode: payload.pinCode,
pinLocked: payload.pinLocked,
pinSkip: payload.pinSkip,
); );
} }

View File

@ -5,24 +5,38 @@ import 'package:meta/meta.dart';
class SettingState { class SettingState {
final String mode; final String mode;
final String tradeType; final String tradeType;
final String pinCode;
final bool pinLocked;
final bool pinSkip;
SettingState({this.mode, this.tradeType});
SettingState({this.mode, this.tradeType, this.pinCode, this.pinLocked, this.pinSkip});
//read hive //read hive
factory SettingState.initial(SettingState payload) { factory SettingState.initial(SettingState payload) {
return SettingState( return SettingState(
mode: payload?.mode ?? SettingModeKassa, mode: payload?.mode ?? SettingModeKassa,
tradeType: payload?.tradeType ?? SettingTradeTypeGood); tradeType: payload?.tradeType ?? SettingTradeTypeGood,
pinCode: payload.pinCode ?? null,
pinLocked: payload.pinLocked ?? false,
pinSkip: false,
);
} }
//write hive //write hive
SettingState copyWith({ SettingState copyWith({
@required mode, @required mode,
@required tradeType, @required tradeType,
@required pinCode,
@required pinLocked,
@required pinSkip,
}) { }) {
return SettingState( return SettingState(
mode: mode ?? this.mode, mode: mode ?? this.mode,
tradeType: tradeType ?? this.tradeType, tradeType: tradeType ?? this.tradeType,
pinCode: pinCode ?? this.pinCode,
pinLocked: pinLocked ?? this.pinLocked,
pinSkip: pinSkip ?? this.pinSkip
); );
} }
@ -31,11 +45,20 @@ class SettingState {
? SettingState( ? SettingState(
tradeType: json['tradeType'], tradeType: json['tradeType'],
mode: json['mode'], mode: json['mode'],
pinCode: json['pinCode'],
pinLocked: json['pinLocked'],
pinSkip: json['pinSkip'],
) )
: null; : null;
} }
dynamic toJson() { dynamic toJson() {
return {"tradeType": tradeType, "mode": mode}; return {
"tradeType": tradeType,
"mode": mode,
"pinCode": pinCode,
"pinLocked" : pinLocked,
"pinSkip" : pinSkip,
};
} }
} }

View File

@ -16,6 +16,7 @@ class UserState {
final Smena smena; final Smena smena;
final Money money; final Money money;
UserState( UserState(
{this.isError, {this.isError,
this.isLoading, this.isLoading,
@ -26,7 +27,8 @@ class UserState {
this.user, this.user,
this.loginFormMessage, this.loginFormMessage,
this.smena, this.smena,
this.money}); this.money,
});
factory UserState.initial(UserState payload) => UserState( factory UserState.initial(UserState payload) => UserState(
isLoading: false, isLoading: false,
@ -73,7 +75,8 @@ class UserState {
user: User.fromJson(json['user']), user: User.fromJson(json['user']),
authenticateType: json['authenticateType'], authenticateType: json['authenticateType'],
login: json['login'], login: json['login'],
password: json['password']) password: json['password'],
)
: null; : null;
} }

View File

@ -7,7 +7,7 @@ const List<Choice> choices = const <Choice>[
//const Choice(title: 'Помощь', icon: Icons.help, command: 'help'), //const Choice(title: 'Помощь', icon: Icons.help, command: 'help'),
const Choice( const Choice(
title: 'Информация о ККМ', icon: Icons.info_outline, command: 'infokkm'), title: 'Информация о ККМ', icon: Icons.info_outline, command: 'infokkm'),
//const Choice(title: 'Язык', icon: Icons.language, command: 'language'), const Choice(title: 'Настройки', icon: Icons.settings, command: 'settings'),
const Choice(title: 'Выйти', icon: Icons.exit_to_app, command: 'exit') const Choice(title: 'Выйти', icon: Icons.exit_to_app, command: 'exit')
]; ];

View File

@ -6,17 +6,21 @@ import 'package:aman_kassa_flutter/core/route_names.dart';
import 'package:aman_kassa_flutter/core/services/ApiService.dart'; import 'package:aman_kassa_flutter/core/services/ApiService.dart';
import 'package:aman_kassa_flutter/core/services/DataService.dart'; import 'package:aman_kassa_flutter/core/services/DataService.dart';
import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart';
import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart';
import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; import 'package:aman_kassa_flutter/redux/actions/user_actions.dart';
import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart';
import 'package:aman_kassa_flutter/redux/state/setting_state.dart'; import 'package:aman_kassa_flutter/redux/state/setting_state.dart';
import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/redux/store.dart';
import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart';
import 'package:aman_kassa_flutter/views/home/components/header_title.dart'; import 'package:aman_kassa_flutter/views/home/components/header_title.dart';
import 'package:aman_kassa_flutter/views/lockscreen/passcodescreen.dart';
import 'package:aman_kassa_flutter/views/start_up/start_up_view.dart';
import 'package:aman_kassa_flutter/widgets/loader/Dialogs.dart'; import 'package:aman_kassa_flutter/widgets/loader/Dialogs.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './tabs/KassaTab.dart'; import './tabs/KassaTab.dart';
import './tabs/AdditionalTab.dart'; import './tabs/AdditionalTab.dart';
@ -30,7 +34,7 @@ class HomeView extends StatefulWidget {
_HomeViewState createState() => _HomeViewState(); _HomeViewState createState() => _HomeViewState();
} }
class _HomeViewState extends State<HomeView> { class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
Logger log = getLogger('HomeView'); Logger log = getLogger('HomeView');
PageController pageController; PageController pageController;
int selectedTabIndex; int selectedTabIndex;
@ -39,6 +43,68 @@ class _HomeViewState extends State<HomeView> {
NavigatorService _navigatorService = locator<NavigatorService>(); NavigatorService _navigatorService = locator<NavigatorService>();
final GlobalKey<State> _keyLoader = new GlobalKey<State>(); final GlobalKey<State> _keyLoader = new GlobalKey<State>();
final lastKnownStateKey = 'lastKnownStateKey';
final backgroundedTimeKey = 'backgroundedTimeKey';
final pinLockMillis = 2000; // 2 seconds
Future _paused() async {
final sp = await SharedPreferences.getInstance();
sp.setInt(lastKnownStateKey, AppLifecycleState.paused.index);
}
Future _inactive() async {
final sp = await SharedPreferences.getInstance();
final prevState = sp.getInt(lastKnownStateKey);
final prevStateIsNotPaused = prevState != null &&
AppLifecycleState.values[prevState] != AppLifecycleState.paused;
if(prevStateIsNotPaused && Redux.store.state.settingState.pinSkip == false) {
// save App backgrounded time to Shared preferences
sp.setInt(backgroundedTimeKey, DateTime.now().millisecondsSinceEpoch);
}
// update previous state as inactive
sp.setInt(lastKnownStateKey, AppLifecycleState.inactive.index);
}
Future _resumed() async {
final sp = await SharedPreferences.getInstance();
final bgTime = sp.getInt(backgroundedTimeKey) ?? 0;
final allowedBackgroundTime = bgTime + pinLockMillis;
final shouldShowPIN = DateTime.now().millisecondsSinceEpoch > allowedBackgroundTime;
print(shouldShowPIN);
if(shouldShowPIN && bgTime > 0) {
Redux.store.dispatch(changePinLockedFromSetting(true));
// show PIN screen
// Navigator.pushReplacement(context, MaterialPageRoute(
// builder: (_) => PassCodeScreen( title: 'Безопасность',)));
Navigator.of(context).push(MaterialPageRoute(
builder: (_) =>
WillPopScope(
onWillPop: () async {
return false;
},
child: PassCodeScreen( title: 'Безопасность',)
)
));
}
sp.remove(backgroundedTimeKey); // clean
sp.setInt(lastKnownStateKey, AppLifecycleState.resumed.index);// previous state
}
_checkLockPin () async {
if ( Redux.store.state.settingState.pinLocked == true) {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) =>
WillPopScope(
onWillPop: () async {
return false;
},
child: PassCodeScreen( title: 'Безопасность',)
)
));
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -46,10 +112,32 @@ class _HomeViewState extends State<HomeView> {
pageController = new PageController(initialPage: selectedTabIndex); pageController = new PageController(initialPage: selectedTabIndex);
Redux.store.dispatch(checkSmena); Redux.store.dispatch(checkSmena);
_dataService.checkDbFill(Redux.store.state.userState.user); _dataService.checkDbFill(Redux.store.state.userState.user);
WidgetsBinding.instance.addObserver(this);
_checkLockPin();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('state = $state');
switch(state) {
case AppLifecycleState.resumed:
_resumed();
break;
case AppLifecycleState.paused:
_paused();
break;
case AppLifecycleState.inactive:
_inactive();
break;
default:
break;
}
} }
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
pageController.dispose(); pageController.dispose();
super.dispose(); super.dispose();
} }
@ -64,6 +152,8 @@ class _HomeViewState extends State<HomeView> {
Navigator.of(_keyLoader.currentContext, rootNavigator: true).pop(); Navigator.of(_keyLoader.currentContext, rootNavigator: true).pop();
} else if (choice.command == 'infokkm') { } else if (choice.command == 'infokkm') {
_navigatorService.push(InfoKkmViewRoute); _navigatorService.push(InfoKkmViewRoute);
} else if (choice.command == 'settings') {
_navigatorService.push(SettingsViewRoute);
} }
} }

View File

@ -6,6 +6,7 @@ import 'package:aman_kassa_flutter/core/services/DataService.dart';
import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; import 'package:aman_kassa_flutter/core/services/dialog_service.dart';
import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart';
import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart'; import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart';
import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart';
import 'package:aman_kassa_flutter/redux/constants/operation_const.dart'; import 'package:aman_kassa_flutter/redux/constants/operation_const.dart';
import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart';
import 'package:aman_kassa_flutter/redux/state/kassa_state.dart'; import 'package:aman_kassa_flutter/redux/state/kassa_state.dart';
@ -183,6 +184,7 @@ class KassaTab extends StatelessWidget {
Future<void> scan() async { Future<void> scan() async {
try { try {
Redux.store.dispatch(changePinSkipFromSetting(true));
var options = ScanOptions(strings: { var options = ScanOptions(strings: {
"cancel": 'Отмена', "cancel": 'Отмена',
"flash_on": 'Вкл фонарик', "flash_on": 'Вкл фонарик',
@ -238,6 +240,8 @@ class KassaTab extends StatelessWidget {
result.rawContent = 'Unknown error: $e'; result.rawContent = 'Unknown error: $e';
_dialogService.showDialog(description: 'Неизвестная ошибка: $e'); _dialogService.showDialog(description: 'Неизвестная ошибка: $e');
} }
} finally {
Redux.store.dispatch(changePinSkipFromSetting(false));
} }
} }

View File

@ -0,0 +1,79 @@
import 'package:aman_kassa_flutter/core/route_names.dart';
import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart';
import 'package:aman_kassa_flutter/redux/store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_lock_screen/flutter_lock_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:aman_kassa_flutter/core/locator.dart';
import 'package:aman_kassa_flutter/core/services/navigator_service.dart';
class PassCodeScreen extends StatefulWidget {
PassCodeScreen({Key key, this.title}) : super(key: key);
final String title;
@override
_PassCodeScreenState createState() => new _PassCodeScreenState();
}
class _PassCodeScreenState extends State<PassCodeScreen> {
// bool isFingerprint = false;
NavigatorService _navigatorService = locator<NavigatorService>();
final backgroundedTimeKey = 'backgroundedTimeKey';
// Future<Null> biometrics() async {
// final LocalAuthentication auth = new LocalAuthentication();
// bool authenticated = false;
//
// try {
// authenticated = await auth.authenticateWithBiometrics(
// localizedReason: 'Scan your fingerprint to authenticate',
// useErrorDialogs: true,
// stickyAuth: false);
// } on PlatformException catch (e) {
// print(e);
// }
// if (!mounted) return;
// if (authenticated) {
// setState(() {
// isFingerprint = true;
// });
// }
// }
@override
Widget build(BuildContext context) {
var myPass = [];
String _pinCode = Redux.store.state.settingState.pinCode;
for (var i = 0; i < _pinCode.length; i++) {
myPass.add(int.parse(_pinCode[i]));
}
return LockScreen(
title: "Введите ПИН-код",
passLength: myPass.length,
bgImage: "assets/images/secBg.jpg",
// fingerPrintImage: "assets/images/finger.png",
// showFingerPass: true,
// fingerFunction: biometrics,
// fingerVerify: isFingerprint,
borderColor: Colors.white,
showWrongPassDialog: true,
wrongPassContent: "Неверный код, повторите попытку",
wrongPassTitle: "Aman Kassa",
wrongPassCancelButtonText: "Отмена",
passCodeVerify: (passcode) async {
for (int i = 0; i < myPass.length; i++) {
if (passcode[i] != myPass[i]) {
return false;
}
}
return true;
},
onSuccess: () {
Redux.store.dispatch(changePinLockedFromSetting(false));
_navigatorService.replace(HomeViewRoute);
});
}
}

View File

@ -0,0 +1,96 @@
import 'package:aman_kassa_flutter/core/locator.dart';
import 'package:aman_kassa_flutter/core/services/dialog_service.dart';
import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart';
import 'package:aman_kassa_flutter/redux/state/setting_state.dart';
import 'package:aman_kassa_flutter/redux/store.dart';
import 'package:aman_kassa_flutter/shared/app_colors.dart';
import 'package:aman_kassa_flutter/shared/ui_helpers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class SettingView extends StatefulWidget {
SettingView();
@override
_SettingViewState createState() => _SettingViewState();
}
class _SettingViewState extends State<SettingView> {
TextEditingController _pinController;
final DialogService _dialogService = locator<DialogService>();
@override
void initState() {
super.initState();
SettingState state = Redux.store.state.settingState;
_pinController = new TextEditingController(text: state.pinCode);
}
@override
void dispose() {
_pinController.dispose();
super.dispose();
}
void _setPinCode(BuildContext _context) async {
FocusScope.of(_context).unfocus();
var value = _pinController.text;
if(value.isEmpty || value.length !=4){
_dialogService.showDialog(description: 'Необходимо указать 4-х значный числовой код');
} else {
await Redux.store.dispatch(changePinCodeFromSetting(_pinController.text));
_dialogService.showDialog(description: 'Данные успешно сохранены');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Настройка HalykPos'),
),
body: SingleChildScrollView(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 14.0),
child: Column(
children: <Widget>[
verticalSpaceTiny,
Text(
'Ддя блокировки приложения пин-кодом, укажите 4-ех значный числовой код',
style: TextStyle(fontSize: 15.0),
textAlign: TextAlign.center,
),
verticalSpaceTiny,
TextField(
controller: _pinController,
maxLength: 4,
obscureText: true,
decoration: InputDecoration(
labelText: 'Пин-код', hintText: "Укажите пин-код"),
keyboardType: TextInputType.number,
),
verticalSpaceMedium,
RaisedButton(
onPressed: () => this._setPinCode(context),
child: Text(
'Cохранить настройки',
style: TextStyle(color: whiteColor, fontSize: 20.0),
),
color: primaryColor,
padding:
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
),
],
),
),
),
);
}
}

View File

@ -139,6 +139,20 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_lock_screen:
dependency: "direct main"
description:
name: flutter_lock_screen
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.8"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.11"
flutter_redux: flutter_redux:
dependency: "direct main" dependency: "direct main"
description: description:
@ -198,6 +212,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "0.16.1"
local_auth:
dependency: "direct main"
description:
name: local_auth
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3+4"
logger: logger:
dependency: "direct main" dependency: "direct main"
description: description:
@ -374,7 +395,7 @@ packages:
source: hosted source: hosted
version: "0.3.0" version: "0.3.0"
shared_preferences: shared_preferences:
dependency: transitive dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

View File

@ -31,6 +31,9 @@ dependencies:
qr_flutter: ^3.2.0 qr_flutter: ^3.2.0
mask_text_input_formatter: ^1.2.1 mask_text_input_formatter: ^1.2.1
flutter_screenutil: ^2.3.1 flutter_screenutil: ^2.3.1
shared_preferences: ^0.5.12+4
flutter_lock_screen: ^1.0.8
local_auth: ^0.6.3+4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@ -40,8 +43,7 @@ flutter:
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
assets: assets:
- assets/images/logo.png - assets/images/
- assets/images/icon_large.png
- assets/lang/en.json - assets/lang/en.json
- assets/lang/ru.json - assets/lang/ru.json
- assets/google_fonts/ - assets/google_fonts/