From 79a0a58735e8eddc43d70e3cecba93f6ad82eeb1 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Sun, 21 Jul 2024 18:34:17 +0500 Subject: [PATCH] close #179 implement the biometric auth system for smart safe locker and merge `wifi_home_screen` and `wifi_remote_screen` into one single module --- .../cubit/wifi_home_cubit.dart | 113 +++++++++++++-- .../cubit/wifi_home_state.dart | 26 +++- .../wifi_home_screen/wifi_home_screen.dart | 131 +++++++++++++----- 3 files changed, 223 insertions(+), 47 deletions(-) diff --git a/lib/screens/wifi_home_screen/cubit/wifi_home_cubit.dart b/lib/screens/wifi_home_screen/cubit/wifi_home_cubit.dart index 3eaa4ed..879c9a8 100644 --- a/lib/screens/wifi_home_screen/cubit/wifi_home_cubit.dart +++ b/lib/screens/wifi_home_screen/cubit/wifi_home_cubit.dart @@ -1,27 +1,120 @@ +import 'dart:developer'; import 'package:bloc/bloc.dart'; -import 'package:http/http.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; import 'package:app_settings/app_settings.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import 'package:smart_link/common/common.dart'; import 'package:smart_link/config/config.dart'; +import 'package:local_auth/error_codes.dart' as biometric_error; +import 'package:local_auth/local_auth.dart'; +import 'package:smart_link/extensions.dart'; part 'wifi_home_state.dart'; -class WifiHomeCubit extends Cubit { +class WifiHomeCubit extends Cubit with StandardAppWidgets { + String baseUrl = "http://${AppStrings.deviceServerIP}"; + final LocalAuthentication _biometricAuth = LocalAuthentication(); + WifiHomeCubit() : super(Initial()); - String? baseUrl; Future connectToESP8266() async { - baseUrl = "http://${AppStrings.deviceServerIP}"; - emit(Connecting()); try { - Response response = await get(Uri.parse(baseUrl!)); - if (response.body == "Connected") { - emit(Connected(baseUrl!)); + safeEmit(Connecting()); + http.Response response = await http.post( + Uri.parse("$baseUrl/connect"), + headers: { + 'Content-Type': 'text/plain', + }, + body: FirebaseAuth.instance.currentUser?.email, + ); + if (response.statusCode == 200) { + safeEmit(Connected(response.body)); + safeEmit(Initial()); + await sendOnMessage(); + return; } + safeEmit(NotConnected(response.body)); + safeEmit(Initial()); + return; } catch (e) { - emit(NotConnected("Connection failed!")); - emit(Initial()); + safeEmit(NotConnected("Please connect to the Smart Lock network!")); + safeEmit(Initial()); await Future.delayed(1.5.seconds); await AppSettings.openAppSettingsPanel(AppSettingsPanelType.wifi); } } + + Future sendOnMessage() async { + try { + bool isAuthenticated = await _isBiometricAuth(); + if (isAuthenticated) { + final response = await http.post( + Uri.parse('$baseUrl/unlock'), + headers: { + 'Content-Type': 'text/plain', + }, + body: FirebaseAuth.instance.currentUser?.email, + ); + + if (response.statusCode == 200) { + safeEmit(OnSignal(response.body, AppColors.primary)); + return; + } + } + } on PlatformException catch (e) { + switch (e.code) { + case biometric_error.lockedOut: + safeEmit(BiometricError(AppStrings.biometricLock)); + + case biometric_error.permanentlyLockedOut: + safeEmit(BiometricError(AppStrings.biometricPermanent)); + + case biometric_error.notEnrolled: + safeEmit(BiometricError(AppStrings.biometricEnrollment)); + + case biometric_error.notAvailable: + safeEmit(BiometricError(AppStrings.biometricNotSupported)); + + default: + } + safeEmit(Initial()); + } on Exception catch (e) { + log(e.toString()); + safeEmit(NetworkError( + "Something went wrong! Please contact support for further assistance.")); + } + } + + Future sendOffMessage() async { + safeEmit(Connecting()); + try { + final response = await http.post( + Uri.parse('$baseUrl/lock'), + headers: { + 'Content-Type': 'text/plain', + }, + body: FirebaseAuth.instance.currentUser?.email, + ); + safeEmit(OffSignal(response.body, Colors.grey)); + } on Exception catch (e) { + log(e.toString()); + safeEmit(NetworkError( + "Something went wrong! Please contact support for further assistance.")); + } finally { + safeEmit(Initial()); + } + } + + Future _isBiometricAuth() async { + return await _biometricAuth.authenticate( + localizedReason: "Biometric authentication required", + options: const AuthenticationOptions( + biometricOnly: true, + stickyAuth: true, + useErrorDialogs: true, + ), + ); + } } diff --git a/lib/screens/wifi_home_screen/cubit/wifi_home_state.dart b/lib/screens/wifi_home_screen/cubit/wifi_home_state.dart index d583168..cb9ea4e 100644 --- a/lib/screens/wifi_home_screen/cubit/wifi_home_state.dart +++ b/lib/screens/wifi_home_screen/cubit/wifi_home_state.dart @@ -7,12 +7,34 @@ class Initial extends WifiHomeState {} class Connecting extends WifiHomeState {} class Connected extends WifiHomeState { - final String baseUrl; + final String message; - Connected(this.baseUrl); + Connected(this.message); } class NotConnected extends WifiHomeState { final String message; NotConnected(this.message); } + +class OnSignal extends WifiHomeState { + final String message; + final Color color; + OnSignal(this.message, this.color); +} + +class OffSignal extends WifiHomeState { + final String message; + final Color color; + OffSignal(this.message, this.color); +} + +class NetworkError extends WifiHomeState { + final String message; + NetworkError(this.message); +} + +class BiometricError extends WifiHomeState { + final String message; + BiometricError(this.message); +} diff --git a/lib/screens/wifi_home_screen/wifi_home_screen.dart b/lib/screens/wifi_home_screen/wifi_home_screen.dart index fafe164..8b5937b 100644 --- a/lib/screens/wifi_home_screen/wifi_home_screen.dart +++ b/lib/screens/wifi_home_screen/wifi_home_screen.dart @@ -3,9 +3,27 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:smart_link/common/common.dart'; import 'package:smart_link/config/config.dart'; import 'package:smart_link/screens/wifi_home_screen/cubit/wifi_home_cubit.dart'; +import 'package:smart_link/services/auth_service.dart'; -class WifiHomeScreen extends StatelessWidget with StandardAppWidgets { - const WifiHomeScreen({super.key}); +class WifiHomeScreen extends StatefulWidget { + final IAuthenticationService authService; + const WifiHomeScreen({super.key, required this.authService}); + + @override + State createState() => _WifiHomeScreenState(); +} + +class _WifiHomeScreenState extends State + with StandardAppWidgets { + @override + void initState() { + super.initState(); + widget.authService.isRevoked().then((blocked) { + if (blocked) { + Navigator.pushReplacementNamed(context, AppRoutes.auth); + } + }); + } @override Widget build(BuildContext context) { @@ -13,51 +31,81 @@ class WifiHomeScreen extends StatelessWidget with StandardAppWidgets { onTap: () => FocusManager.instance.primaryFocus?.unfocus(), child: Scaffold( appBar: AppBar( - title: const Text("Locker Home"), + title: const Text("Smart Lock"), actions: [ popupMenuButtonWidget(context), ], ), drawer: AppDrawer(), - body: SingleChildScrollView( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - height: MediaQuery.of(context).size.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - BlocConsumer( - builder: _blocBuilders, - listener: _blocListeners, - ), - SizedBox(height: MediaQuery.of(context).size.height / 30), - Text( - AppStrings.lockerHomeInfo, - textAlign: TextAlign.justify, - style: Theme.of(context).textTheme.labelSmall!, - ), - ], - ), + body: SizedBox( + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BlocConsumer( + builder: _blocBuilders, + listener: _blocListeners, + ), + ], ), ), ), ); } + Center _biometricAuthButton( + BuildContext context, + Future Function()? cb, [ + Color color = Colors.grey, + ]) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.fingerprint_rounded, + size: MediaQuery.of(context).size.height / 5, + color: color, + ), + onPressed: cb != null ? () async => await cb() : null, + ), + const Text( + "Tap to start Authentication process", + style: TextStyle( + fontSize: 11, + color: Colors.grey, + ), + ) + ], + ), + ); + } + Widget _blocBuilders(BuildContext context, WifiHomeState state) { switch (state) { case Initial(): - return Center( - child: ElevatedButton( - child: const Text('Connect'), - onPressed: () { - context.read().connectToESP8266(); - }, - ), + return _biometricAuthButton( + context, + context.read().connectToESP8266, ); case Connecting(): - return const Center(child: CircularProgressIndicator()); + return _biometricAuthButton(context, null); + + case OnSignal(): + return _biometricAuthButton( + context, + context.read().sendOffMessage, + state.color, + ); + + case OffSignal(): + return _biometricAuthButton( + context, + context.read().sendOnMessage, + state.color, + ); default: return Container(); @@ -66,16 +114,29 @@ class WifiHomeScreen extends StatelessWidget with StandardAppWidgets { void _blocListeners(BuildContext context, WifiHomeState state) { switch (state) { - case Connected(): - Navigator.pushNamed( + case BiometricError(): + showSnackBarWidget(context, state.message); + break; + + case NetworkError(): + showSnackBarWidget(context, state.message); + break; + + case NotConnected(): + showSnackBarWidget(context, state.message); + break; + + case OnSignal(): + showSnackBarWidget( context, - AppRoutes.wifiRemote, - arguments: state.baseUrl, + state.message, + color: Colors.green.shade700, ); break; - case NotConnected(): + case OffSignal(): showSnackBarWidget(context, state.message); + break; default: }