Skip to content

Commit

Permalink
close #179 implement the biometric auth system for smart safe locker …
Browse files Browse the repository at this point in the history
…and merge `wifi_home_screen` and `wifi_remote_screen` into one single module
  • Loading branch information
mediocre9 committed Jul 21, 2024
1 parent 867d6f3 commit 79a0a58
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 47 deletions.
113 changes: 103 additions & 10 deletions lib/screens/wifi_home_screen/cubit/wifi_home_cubit.dart
Original file line number Diff line number Diff line change
@@ -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<WifiHomeState> {
class WifiHomeCubit extends Cubit<WifiHomeState> with StandardAppWidgets {
String baseUrl = "http://${AppStrings.deviceServerIP}";
final LocalAuthentication _biometricAuth = LocalAuthentication();

WifiHomeCubit() : super(Initial());
String? baseUrl;

Future<void> 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<void> 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<void> 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<bool> _isBiometricAuth() async {
return await _biometricAuth.authenticate(
localizedReason: "Biometric authentication required",
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
useErrorDialogs: true,
),
);
}
}
26 changes: 24 additions & 2 deletions lib/screens/wifi_home_screen/cubit/wifi_home_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
131 changes: 96 additions & 35 deletions lib/screens/wifi_home_screen/wifi_home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,109 @@ 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<WifiHomeScreen> createState() => _WifiHomeScreenState();
}

class _WifiHomeScreenState extends State<WifiHomeScreen>
with StandardAppWidgets {
@override
void initState() {
super.initState();
widget.authService.isRevoked().then((blocked) {
if (blocked) {
Navigator.pushReplacementNamed(context, AppRoutes.auth);
}
});
}

@override
Widget build(BuildContext context) {
return GestureDetector(
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<WifiHomeCubit, WifiHomeState>(
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<WifiHomeCubit, WifiHomeState>(
builder: _blocBuilders,
listener: _blocListeners,
),
],
),
),
),
);
}

Center _biometricAuthButton(
BuildContext context,
Future<void> 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<WifiHomeCubit>().connectToESP8266();
},
),
return _biometricAuthButton(
context,
context.read<WifiHomeCubit>().connectToESP8266,
);

case Connecting():
return const Center(child: CircularProgressIndicator());
return _biometricAuthButton(context, null);

case OnSignal():
return _biometricAuthButton(
context,
context.read<WifiHomeCubit>().sendOffMessage,
state.color,
);

case OffSignal():
return _biometricAuthButton(
context,
context.read<WifiHomeCubit>().sendOnMessage,
state.color,
);

default:
return Container();
Expand All @@ -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:
}
Expand Down

0 comments on commit 79a0a58

Please sign in to comment.