From da07b3b57126748e0912394bdc4aff2b74f226f8 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:33:12 +0500 Subject: [PATCH 01/14] chore: add generated `.mock.test` files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 52832a3..1147fee 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ app.*.map.json /android/app/profile /android/app/release +*.mocks.dart # remove unwanted platforms windows/ @@ -52,4 +53,4 @@ web/ ios/ # remove firebase options -firebase_options.dart \ No newline at end of file +firebase_options.dart From d3cb93e557072fcc10e83493c256bb8bf7061aad Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:38:06 +0500 Subject: [PATCH 02/14] resolves #55 refactor callback hell by applying dependency injection and SRP principle --- .../cubit/bluetooth_home_cubit.dart | 186 ++++++------------ .../cubit/bluetooth_home_state.dart | 21 +- 2 files changed, 65 insertions(+), 142 deletions(-) diff --git a/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_cubit.dart b/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_cubit.dart index 9d3db00..61079fd 100644 --- a/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_cubit.dart +++ b/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_cubit.dart @@ -1,161 +1,91 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; -import 'package:permission_handler/permission_handler.dart'; import '../../../config/strings/strings.dart'; +import '../../../services/bluetooth_service.dart'; +import '../../../services/permission_service.dart'; part 'bluetooth_home_state.dart'; class BluetoothHomeCubit extends Cubit { - final FlutterBluetoothSerial _bluetooth = FlutterBluetoothSerial.instance; - final List _foundedDevices = []; - final List _pairedDevices = []; - - BluetoothHomeCubit() - : super( - Initial( - text: Strings.bluetoothOnHomeDescription, - icon: Icons.bluetooth_rounded, - ), - ) { - _grantPermissions(); + final BluetoothPermissionService _permission; + final BluetoothService _bluetooth; + final List _discovered = []; + final List _paired = []; + + BluetoothHomeCubit( + this._permission, + this._bluetooth, + ) : super(Initial(text: Strings.bluetoothOnHomeDescription, icon: Icons.bluetooth_rounded)) { + init(); } - Future _grantPermissions() async { - PermissionStatus connectStatus = - await Permission.bluetoothConnect.request(); - PermissionStatus scanStatus = await Permission.bluetoothScan.request(); + Future init() async { + bool hasBluetoothPermission = await _permission.requestPermissions(); + bool? isBluetoothEnabled = await _bluetooth.enableBluetooth(); - if (connectStatus.isGranted && scanStatus.isGranted) { + if (hasBluetoothPermission && isBluetoothEnabled!) { await _getPairedDevices(); } else { emit(BluetoothDisabled(message: Strings.bluetoothOff)); + emit(Initial(text: Strings.bluetoothOnHomeDescription, icon: Icons.bluetooth_rounded)); } } - Future get isBluetoothEnabled async => _bluetooth.isEnabled; + Future startScan() async { + bool? isBluetoothEnabled = await _bluetooth.enableBluetooth(); - /// retrieves all paired devices. - Future _getPairedDevices() async { - _pairedDevices.clear(); - if ((await isBluetoothEnabled)!) { - emit(GetPairedDevices(text: 'Getting paired devices . . .')); - await _bluetooth - .getBondedDevices() - .then((paired) => _pairedDevices.addAll(paired)) - .whenComplete(() { - if (_pairedDevices.isNotEmpty) { - emit(ShowPairedDevices( - totalPairedDevices: _pairedDevices.length, - pairedDevices: _pairedDevices, - )); - } else { - emit( - Initial( - text: Strings.bluetoothOnHomeDescription, - icon: Icons.bluetooth_rounded, - ), - ); - } - }); - } else { - await _bluetooth.requestEnable().then((isBluetoothTurnedOn) { - if (isBluetoothTurnedOn!) { - _getPairedDevices(); - } else { - emit( - Initial( - text: Strings.bluetoothOnHomeDescription, - icon: Icons.bluetooth_rounded, - ), - ); - } - }); + if (!isBluetoothEnabled!) { + emit(BluetoothDisabled(message: Strings.bluetoothOff)); + emit(Initial(text: Strings.bluetoothOnHomeDescription, icon: Icons.bluetooth_rounded)); + return; } - } - /// cancel discovery operation . . . - Future stopDiscovery() async { - await _bluetooth.cancelDiscovery(); - _getPairedDevices(); - } + emit(DiscoverDevices(text: Strings.bluetoothDiscoveryDescription)); + _discovered.clear(); - /// finds nearby beacon bluetooth devices for pairing. - Future discoverDevices() async { - _foundedDevices.clear(); - if ((await isBluetoothEnabled)!) { - emit(DiscoverNewDevices(text: Strings.bluetoothDiscoveryDescription)); - _bluetooth.startDiscovery().listen((e) { - _foundedDevices - ..clear() - ..add(e.device); - }).onDone(() { - if (_foundedDevices.isNotEmpty) { - emit(ShowDiscoveredDevices( - totalDiscoveredDevices: _foundedDevices.length, - discoveredDevices: _foundedDevices, - )); - return; - } else if (_pairedDevices.isNotEmpty) { - emit(HasNotFoundNewDevices(message: Strings.devicesNotInRange)); - emit(ShowPairedDevices( - totalPairedDevices: _pairedDevices.length, - pairedDevices: _pairedDevices, - )); - return; - } else { - emit(HasNotFoundNewDevices(message: Strings.devicesNotInRange)); - emit( - Initial( - text: Strings.bluetoothOnHomeDescription, - icon: Icons.bluetooth_rounded, - ), - ); - } - }); + await for (final result in _bluetooth.startDiscovery()) { + _discovered.add(result.device); + } + + if (_discovered.isNotEmpty) { + emit(ShowDiscoveredDevices(devices: _discovered)); } else { - await _bluetooth.requestEnable().then((isBluetoothTurnedOn) { - if (isBluetoothTurnedOn!) { - _getPairedDevices(); - } else { - emit(BluetoothDisabled(message: Strings.bluetoothOff)); - emit(Initial( - text: Strings.bluetoothOnHomeDescription, - icon: Icons.bluetooth_rounded, - )); - } - }); + emit(HasNotFoundNewDevices(message: Strings.devicesNotInRange)); + await _getPairedDevices(); } } - /// Tries to pair with discovered unpaired device. + Future stopScan() async { + await _bluetooth.cancelDiscovery(); + await _getPairedDevices(); + } + Future pairDevice(BluetoothDevice device) async { - if (!device.isBonded) { - emit(PairDevice( - text: 'Trying to pair with ${device.name}. Please be patient!', + emit(PairDevice(text: 'Trying to pair with ${device.name}. Please be patient!')); + bool? isBonded = await _bluetooth.bondDevice(device); + + if (!isBonded!) { + emit(PairUnsuccessful( + message: Strings.pairUnsuccessful, + snackbarColor: Colors.deepOrange, )); - await _bluetooth.bondDeviceAtAddress(device.address).then((connState) { - if (connState!) { - device; - emit(PairSuccessful( - message: 'Paired to ${device.name}!', - snackbarColor: Colors.green, - )); - emit(ConnectToRemoteDevice(device: device)); - } else { - emit(PairUnsuccessful( - message: 'Couldn`t pair with ${device.name}!', - snackbarColor: Colors.deepOrange, - )); - emit(ShowPairedDevices( - totalPairedDevices: _foundedDevices.length, - pairedDevices: _foundedDevices, - )); - } - }); + emit(ShowPairedDevices(devices: _paired)); } else { emit(ConnectToRemoteDevice(device: device)); } } + + Future _getPairedDevices() async { + emit(GetPairedDevices(text: Strings.gettingPairedDevices)); + + var devices = await _bluetooth.fetchPairedDevices(); + _paired + ..clear() + ..addAll(devices); + + var currentState = _paired.isNotEmpty ? ShowPairedDevices(devices: _paired) : Initial(text: Strings.bluetoothOnHomeDescription, icon: Icons.bluetooth_rounded); + emit(currentState); + } } diff --git a/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_state.dart b/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_state.dart index 8b62926..566832b 100644 --- a/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_state.dart +++ b/lib/screens/bluetooth_home_screen/cubit/bluetooth_home_state.dart @@ -18,9 +18,9 @@ class BluetoothDisabled extends BluetoothHomeState { BluetoothDisabled({required this.message}); } -class DiscoverNewDevices extends BluetoothHomeState { +class DiscoverDevices extends BluetoothHomeState { final String text; - DiscoverNewDevices({required this.text}); + DiscoverDevices({required this.text}); } class HasNotFoundNewDevices extends BluetoothHomeState { @@ -36,13 +36,9 @@ class ConnectToRemoteDevice extends BluetoothHomeState { } class ShowDiscoveredDevices extends BluetoothHomeState { - final List discoveredDevices; - final int totalDiscoveredDevices; + final List devices; - ShowDiscoveredDevices({ - required this.totalDiscoveredDevices, - required this.discoveredDevices, - }); + ShowDiscoveredDevices({required this.devices}); } class PairDevice extends BluetoothHomeState { @@ -53,6 +49,7 @@ class PairDevice extends BluetoothHomeState { class PairUnsuccessful extends BluetoothHomeState { final String message; final Color snackbarColor; + PairUnsuccessful({ required this.snackbarColor, required this.message, @@ -75,11 +72,7 @@ class GetPairedDevices extends BluetoothHomeState { } class ShowPairedDevices extends BluetoothHomeState { - final List pairedDevices; - final int totalPairedDevices; + final List devices; - ShowPairedDevices({ - required this.totalPairedDevices, - required this.pairedDevices, - }); + ShowPairedDevices({required this.devices}); } From 86a87b319606383c1dd9317101a09fb2f7cb0ddf Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:43:46 +0500 Subject: [PATCH 03/14] refactor: code to separate the `_grantPermissions` method from `bluetooth_home_cubit` as a separate service class for modularity --- lib/services/permission_service.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/services/permission_service.dart diff --git a/lib/services/permission_service.dart b/lib/services/permission_service.dart new file mode 100644 index 0000000..9f2d569 --- /dev/null +++ b/lib/services/permission_service.dart @@ -0,0 +1,18 @@ +import 'package:permission_handler/permission_handler.dart'; + +abstract interface class IPermissionService { + Future requestPermissions(); +} + +class BluetoothPermissionService implements IPermissionService { + @override + Future requestPermissions() async { + List permissions = await Future.wait([ + Permission.bluetoothConnect.request(), + Permission.bluetoothScan.request(), + Permission.location.request(), + ]); + + return permissions.every((permission) => permission.isGranted); + } +} From 6c32f517112e3bf3bc4227e9fb3c609898d29f10 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:45:56 +0500 Subject: [PATCH 04/14] refactor: extracts bluetooth related methods as dedicated service class. --- lib/services/bluetooth_service.dart | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/services/bluetooth_service.dart diff --git a/lib/services/bluetooth_service.dart b/lib/services/bluetooth_service.dart new file mode 100644 index 0000000..2393dea --- /dev/null +++ b/lib/services/bluetooth_service.dart @@ -0,0 +1,44 @@ +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; + +abstract interface class IBluetoothService { + Stream startDiscovery(); + + Future> fetchPairedDevices(); + + Future enableBluetooth(); + + Future bondDevice(BluetoothDevice device); + + Future cancelDiscovery(); +} + +class BluetoothService implements IBluetoothService { + final FlutterBluetoothSerial _bluetooth; + + BluetoothService(this._bluetooth); + + @override + Stream startDiscovery() { + return _bluetooth.startDiscovery(); + } + + @override + Future cancelDiscovery() async { + await _bluetooth.cancelDiscovery(); + } + + @override + Future> fetchPairedDevices() async { + return await _bluetooth.getBondedDevices(); + } + + @override + Future bondDevice(BluetoothDevice device) async { + return device.isBonded ? true : await _bluetooth.bondDeviceAtAddress(device.address); + } + + @override + Future enableBluetooth() async { + return await _bluetooth.requestEnable(); + } +} From aa8479cf3e35f90798f65ab01d0e28b768f8aca4 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:48:18 +0500 Subject: [PATCH 05/14] refactor: fix and increase code indentation --- .../biometric_screen/fingerprint_screen.dart | 12 +- .../bluetooth_remote_screen.dart | 131 +++++------------- 2 files changed, 41 insertions(+), 102 deletions(-) diff --git a/lib/screens/biometric_screen/fingerprint_screen.dart b/lib/screens/biometric_screen/fingerprint_screen.dart index 9c31cdf..f1bc7ff 100644 --- a/lib/screens/biometric_screen/fingerprint_screen.dart +++ b/lib/screens/biometric_screen/fingerprint_screen.dart @@ -19,8 +19,7 @@ class BiometricScreen extends StatefulWidget { State createState() => _BiometricScreenState(); } -class _BiometricScreenState extends State - with StandardAppWidgets { +class _BiometricScreenState extends State with StandardAppWidgets { bool isAuthenticated = false; Future on() async { @@ -110,18 +109,13 @@ class _BiometricScreenState extends State onPressed: () => _startAuthentication(), icon: const Icon(Icons.fingerprint_rounded), iconSize: mediaQuery.size.height / 5, - color: isAuthenticated - ? AppColors.primary - : const Color.fromARGB(255, 107, 107, 107), + color: isAuthenticated ? AppColors.primary : const Color.fromARGB(255, 107, 107, 107), ), OutlinedButton( onPressed: () => off(), child: Text( "Switch Off", - style: TextStyle( - color: isAuthenticated - ? AppColors.primary - : const Color.fromARGB(255, 107, 107, 107)), + style: TextStyle(color: isAuthenticated ? AppColors.primary : const Color.fromARGB(255, 107, 107, 107)), ), ), ], diff --git a/lib/screens/bluetooth_remote_screen/bluetooth_remote_screen.dart b/lib/screens/bluetooth_remote_screen/bluetooth_remote_screen.dart index 5e025af..8bc4aca 100644 --- a/lib/screens/bluetooth_remote_screen/bluetooth_remote_screen.dart +++ b/lib/screens/bluetooth_remote_screen/bluetooth_remote_screen.dart @@ -5,6 +5,8 @@ import 'package:smart_link/config/index.dart'; import 'cubit/bluetooth_remote_cubit.dart'; +/// [Refactoring needed] +/// Priority-Level: 2 class BluetoothRemoteScreen extends StatefulWidget { final BluetoothDevice device; const BluetoothRemoteScreen({super.key, required this.device}); @@ -18,8 +20,7 @@ class _BluetoothRemoteScreenState extends State { @override void initState() { () async { - await BlocProvider.of(context) - .connect(widget.device); + await BlocProvider.of(context).connect(widget.device); }(); super.initState(); } @@ -51,9 +52,7 @@ class _BluetoothRemoteScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), - backgroundColor: (state.message == "Connected!") - ? Colors.green - : Colors.red, + backgroundColor: (state.message == "Connected!") ? Colors.green : Colors.red, ), ); } @@ -69,23 +68,16 @@ class _BluetoothRemoteScreenState extends State { OutlinedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( - robotControlButtons[0] - ? AppColors.elevatedButton - : Colors.transparent, + robotControlButtons[0] ? AppColors.elevatedButton : Colors.transparent, ), foregroundColor: MaterialStateProperty.all( - robotControlButtons[0] - ? AppColors.elevatedButtonContent - : AppColors.elevatedButton, + robotControlButtons[0] ? AppColors.elevatedButtonContent : AppColors.elevatedButton, ), ), onPressed: () { setState(() { - robotControlButtons - .setAll(0, [true, false, false, false]); - context - .read() - .moveForward(); + robotControlButtons.setAll(0, [true, false, false, false]); + context.read().moveForward(); }); }, child: const Icon(Icons.arrow_circle_up_rounded), @@ -96,87 +88,61 @@ class _BluetoothRemoteScreenState extends State { OutlinedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( - robotControlButtons[1] - ? AppColors.elevatedButton - : Colors.transparent, + robotControlButtons[1] ? AppColors.elevatedButton : Colors.transparent, ), foregroundColor: MaterialStateProperty.all( - robotControlButtons[1] - ? AppColors.elevatedButtonContent - : AppColors.elevatedButton, + robotControlButtons[1] ? AppColors.elevatedButtonContent : AppColors.elevatedButton, ), ), onPressed: () { setState(() { - robotControlButtons - .setAll(0, [false, true, false, false]); + robotControlButtons.setAll(0, [false, true, false, false]); - context - .read() - .moveLeft(); + context.read().moveLeft(); }); }, - child: - const Icon(Icons.arrow_circle_left_outlined), + child: const Icon(Icons.arrow_circle_left_outlined), ), OutlinedButton( child: const Icon(Icons.restart_alt_outlined), onPressed: () { setState(() { - robotControlButtons.setAll( - 0, [false, false, false, false]); - context - .read() - .reset(); + robotControlButtons.setAll(0, [false, false, false, false]); + context.read().reset(); }); }), OutlinedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( - robotControlButtons[2] - ? AppColors.elevatedButton - : Colors.transparent, + robotControlButtons[2] ? AppColors.elevatedButton : Colors.transparent, ), foregroundColor: MaterialStateProperty.all( - robotControlButtons[2] - ? AppColors.elevatedButtonContent - : AppColors.elevatedButton, + robotControlButtons[2] ? AppColors.elevatedButtonContent : AppColors.elevatedButton, ), ), onPressed: () { setState(() { - robotControlButtons - .setAll(0, [false, false, true, false]); - context - .read() - .moveRight(); + robotControlButtons.setAll(0, [false, false, true, false]); + context.read().moveRight(); }); }, - child: - const Icon(Icons.arrow_circle_right_outlined), + child: const Icon(Icons.arrow_circle_right_outlined), ), ], ), OutlinedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( - robotControlButtons[3] - ? AppColors.elevatedButton - : Colors.transparent, + robotControlButtons[3] ? AppColors.elevatedButton : Colors.transparent, ), foregroundColor: MaterialStateProperty.all( - robotControlButtons[3] - ? AppColors.elevatedButtonContent - : AppColors.elevatedButton, + robotControlButtons[3] ? AppColors.elevatedButtonContent : AppColors.elevatedButton, ), ), onPressed: () { setState(() { - robotControlButtons - .setAll(0, [false, false, false, true]); - context - .read() - .moveBackward(); + robotControlButtons.setAll(0, [false, false, false, true]); + context.read().moveBackward(); }); }, child: const Icon(Icons.arrow_circle_down_outlined), @@ -185,16 +151,12 @@ class _BluetoothRemoteScreenState extends State { const Divider(), GestureDetector( onLongPressStart: (details) { - context - .read() - .moveCameraAngleToUp(); + context.read().moveCameraAngleToUp(); }, - onLongPressEnd: (details) => - context.read().stopCamera(), + onLongPressEnd: (details) => context.read().stopCamera(), child: OutlinedButton( onPressed: () {}, - child: const Icon( - Icons.keyboard_double_arrow_up_rounded), + child: const Icon(Icons.keyboard_double_arrow_up_rounded), ), ), Row( @@ -202,48 +164,34 @@ class _BluetoothRemoteScreenState extends State { children: [ GestureDetector( onLongPressStart: (details) { - context - .read() - .moveCameraAngleToLeft(); + context.read().moveCameraAngleToLeft(); }, - onLongPressEnd: (details) => context - .read() - .stopCamera(), + onLongPressEnd: (details) => context.read().stopCamera(), child: OutlinedButton( onPressed: () {}, - child: const Icon( - Icons.keyboard_double_arrow_left_rounded), + child: const Icon(Icons.keyboard_double_arrow_left_rounded), ), ), GestureDetector( onLongPressStart: (details) { - context - .read() - .moveCameraAngleToRight(); + context.read().moveCameraAngleToRight(); }, - onLongPressEnd: (details) => context - .read() - .stopCamera(), + onLongPressEnd: (details) => context.read().stopCamera(), child: OutlinedButton( onPressed: () {}, - child: const Icon( - Icons.keyboard_double_arrow_right_rounded), + child: const Icon(Icons.keyboard_double_arrow_right_rounded), ), ), ], ), GestureDetector( onLongPressStart: (details) { - context - .read() - .moveCameraAngleToDown(); + context.read().moveCameraAngleToDown(); }, - onLongPressEnd: (details) => - context.read().stopCamera(), + onLongPressEnd: (details) => context.read().stopCamera(), child: OutlinedButton( onPressed: () {}, - child: const Icon( - Icons.keyboard_double_arrow_down_rounded), + child: const Icon(Icons.keyboard_double_arrow_down_rounded), ), ), SizedBox(height: mediaQuery.size.height / 20), @@ -251,16 +199,13 @@ class _BluetoothRemoteScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 30), child: Row( children: [ - BlocBuilder( + BlocBuilder( builder: (context, state) { if (state is ConnectedToBluetoothDevice) { return Expanded( child: ElevatedButton( onPressed: () { - context - .read() - .disconnect(); + context.read().disconnect(); Navigator.pushReplacementNamed( context, Routes.bluetoothHome, From bdfaed2f9381dc77ce3dae7dbcc9207a22b4240f Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:50:21 +0500 Subject: [PATCH 06/14] refactor: variable, class and method naming improvements --- .../bluetooth_home_screen.dart | 18 ++++++------------ .../widgets/devices_listview.dart | 14 +++++--------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/lib/screens/bluetooth_home_screen/bluetooth_home_screen.dart b/lib/screens/bluetooth_home_screen/bluetooth_home_screen.dart index 3fc989e..f0c1f16 100644 --- a/lib/screens/bluetooth_home_screen/bluetooth_home_screen.dart +++ b/lib/screens/bluetooth_home_screen/bluetooth_home_screen.dart @@ -53,15 +53,15 @@ class BluetoothHomeScreen extends StatelessWidget with StandardAppWidgets { return FloatingActionButton( child: const Icon(Icons.play_arrow_rounded), onPressed: () { - context.read().discoverDevices(); + context.read().startScan(); }, ); - case DiscoverNewDevices(): + case DiscoverDevices(): return FloatingActionButton( child: const Icon(Icons.pause), onPressed: () { - context.read().stopDiscovery(); + context.read().stopScan(); }, ); @@ -117,16 +117,10 @@ class BluetoothHomeScreen extends StatelessWidget with StandardAppWidgets { ); case ShowPairedDevices(): - return DevicesListView( - bluetoothDevices: state.pairedDevices, - totalDevices: state.totalPairedDevices, - ); + return DevicesListView(devices: state.devices); case ShowDiscoveredDevices(): - return DevicesListView( - bluetoothDevices: state.discoveredDevices, - totalDevices: state.totalDiscoveredDevices, - ); - case DiscoverNewDevices(): + return DevicesListView(devices: state.devices); + case DiscoverDevices(): return RadarAnimation(text: state.text); case PairDevice(): diff --git a/lib/screens/bluetooth_home_screen/widgets/devices_listview.dart b/lib/screens/bluetooth_home_screen/widgets/devices_listview.dart index e6f5ddd..6b1c872 100644 --- a/lib/screens/bluetooth_home_screen/widgets/devices_listview.dart +++ b/lib/screens/bluetooth_home_screen/widgets/devices_listview.dart @@ -6,27 +6,23 @@ import '../cubit/bluetooth_home_cubit.dart'; import 'card_tile.dart'; class DevicesListView extends StatelessWidget { - final List bluetoothDevices; - final int totalDevices; + final List devices; const DevicesListView({ super.key, - required this.bluetoothDevices, - required this.totalDevices, + required this.devices, }); @override Widget build(BuildContext context) { return Expanded( child: ListView.builder( - itemCount: bluetoothDevices.length, + itemCount: devices.length, itemBuilder: (context, i) { return CardTile( - device: bluetoothDevices[i], + device: devices[i], onPressed: () { - context - .read() - .pairDevice(bluetoothDevices[i]); + context.read().pairDevice(devices[i]); }, ); }, From cabd32d3069b2d6cb41436400a71c83583a8d83e Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:51:23 +0500 Subject: [PATCH 07/14] refactor: create and add string constants --- lib/config/strings/strings.dart | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/config/strings/strings.dart b/lib/config/strings/strings.dart index 4196a47..d38e65c 100644 --- a/lib/config/strings/strings.dart +++ b/lib/config/strings/strings.dart @@ -6,22 +6,18 @@ class Strings { static const String appDescription = 'IoT Remote Control'; static const String appLogo = 'assets/images/logo.png'; static const String bluetoothOnHomeDescription = 'Scan for bluetooth devices'; - static const String bluetoothDiscoveryDescription = - 'This may take a few moments to discover nearby devices. Please be patient!'; + static const String bluetoothDiscoveryDescription = 'This may take a few moments to discover nearby devices. Please be patient!'; + static const String gettingPairedDevices = 'Getting paired devices . . .'; + static const String pairUnsuccessful = ""; static const String noInternet = 'No Internet Connection!'; static const String bluetoothOff = 'Bluetooth service is off.'; - static const String devicesNotInRange = - 'No nearby device(s) available. Try Again!'; - static const String copyright = - '(c) Copyright 2023 CUSIT IT & Robotics Engineering Society. All rights reserved.'; + static const String devicesNotInRange = 'No nearby device(s) available. Try Again!'; + static const String copyright = '(c) Copyright 2023 CUSIT IT & Robotics Engineering Society. All rights reserved.'; static const String biometricLock = "Locked out due to too many attempts!"; - static const String biometricPermanent = - "Locked out permanently due to too many attempts!"; - static const String biometricEnrollment = - "Please register your fingerprint from your device settings!"; - static const String biometricNotSupported = - "Your device does not have fingerprint support!"; + static const String biometricPermanent = "Locked out permanently due to too many attempts!"; + static const String biometricEnrollment = "Please register your fingerprint from your device settings!"; + static const String biometricNotSupported = "Your device does not have fingerprint support!"; static const String microControllerIp = "192.168.4.1"; static const String userBlocked = 'Your account has been blocked.'; From 54f62d2d638fe1addbac14f6743d7691c3f60490 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:53:24 +0500 Subject: [PATCH 08/14] refactor: use dependency injection in `Routes.bluetoothHome` route --- lib/config/router/route_generator.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/config/router/route_generator.dart b/lib/config/router/route_generator.dart index 7c0cbe1..3abf29c 100644 --- a/lib/config/router/route_generator.dart +++ b/lib/config/router/route_generator.dart @@ -5,6 +5,8 @@ import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:smart_link/config/router/index.dart'; import 'package:smart_link/screens/authentication_screen/cubit/authentication_screen_cubit.dart'; import 'package:smart_link/services/auth_service.dart'; +import 'package:smart_link/services/bluetooth_service.dart'; +import 'package:smart_link/services/permission_service.dart'; import '../../screens/wifi_home_screen/cubit/wifi_home_cubit.dart'; class RouteGenerator { @@ -28,7 +30,10 @@ class RouteGenerator { return _pageTransition( BlocProvider( lazy: false, - create: (_) => BluetoothHomeCubit(), + create: (_) => BluetoothHomeCubit( + BluetoothPermissionService(), + BluetoothService(FlutterBluetoothSerial.instance), + ), child: const BluetoothHomeScreen(), ), ); @@ -68,8 +73,7 @@ class RouteGenerator { } } - static _pageTransition(Widget route) => - CupertinoPageRoute(builder: (_) => route); + static _pageTransition(Widget route) => CupertinoPageRoute(builder: (_) => route); static _defaultRoute() { return const Scaffold( From 2f91f9b98d393c387d0356e4772908f0c1500523 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:55:32 +0500 Subject: [PATCH 09/14] refactor: conditional initialization of initial route based on `kDebugMode` --- lib/main.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 20d94a9..34e78e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:smart_link/config/router/route_generator.dart'; import 'firebase_options.dart'; @@ -31,9 +32,9 @@ class SmartLinkApp extends StatelessWidget { return MaterialApp( title: Strings.appName, color: AppColors.primary, - initialRoute: Routes.auth, + initialRoute: kDebugMode ? Routes.bluetoothHome : Routes.auth, onGenerateRoute: RouteGenerator.generate, - debugShowCheckedModeBanner: false, + debugShowCheckedModeBanner: kDebugMode ? true : false, theme: AppTheme.darkTheme(), ); } From 50865d9fa25fbeebde6d1cb1c6edcb9cf37c847d Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:56:57 +0500 Subject: [PATCH 10/14] test: add test cases for `BluetoothPermissionService` --- test/permission_service_test.dart | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/permission_service_test.dart diff --git a/test/permission_service_test.dart b/test/permission_service_test.dart new file mode 100644 index 0000000..8f0eb99 --- /dev/null +++ b/test/permission_service_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:smart_link/services/permission_service.dart'; +import 'permission_service_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('BluetoothPermissionService tests', () { + late IPermissionService permissionService; + + setUp(() { + permissionService = MockBluetoothPermissionService(); + }); + + test('requestPermissions should return true if all permissions are granted', () async { + when(permissionService.requestPermissions()).thenAnswer((_) async => true); + + final bool result = await permissionService.requestPermissions(); + + verify(permissionService.requestPermissions()).called(1); + + expect(result, true); + }); + + test('requestPermissions should return false if any permission is not granted', () async { + when(permissionService.requestPermissions()).thenAnswer((_) async => false); + + final bool result = await permissionService.requestPermissions(); + + verify(permissionService.requestPermissions()).called(1); + + expect(result, false); + }); + }); +} From ef47c29a00ee14d573c8f4ff3b1fb35da89eb96c Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:58:08 +0500 Subject: [PATCH 11/14] test: add test cases for `BluetoothService` --- test/bluetooth_service_test.dart | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 test/bluetooth_service_test.dart diff --git a/test/bluetooth_service_test.dart b/test/bluetooth_service_test.dart new file mode 100644 index 0000000..d7c450f --- /dev/null +++ b/test/bluetooth_service_test.dart @@ -0,0 +1,56 @@ +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:smart_link/services/bluetooth_service.dart'; + +import 'bluetooth_service_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + List dummyDevices = List.unmodifiable([ + const BluetoothDevice( + name: "Device 1", + address: "00-B0-D0-00-C2-26", + ), + const BluetoothDevice( + name: "Device 2", + address: "00-10-D0-02-C2-26", + ), + ]); + + group("BluetoothService Test", () { + late IBluetoothService bluetoothService; + + setUp(() { + bluetoothService = MockBluetoothService(); + }); + + test("fetchPairedDevices should return list of paired devices", () async { + when(bluetoothService.fetchPairedDevices()).thenAnswer((_) async => dummyDevices); + + final List result = await bluetoothService.fetchPairedDevices(); + + verify(bluetoothService.fetchPairedDevices()).called(1); + expect(result, dummyDevices); + }); + + test("enableBluetooth should return true if user-permission granted", () async { + when(bluetoothService.enableBluetooth()).thenAnswer((_) async => true); + + final bool? result = await bluetoothService.enableBluetooth(); + + verify(bluetoothService.enableBluetooth()).called(1); + expect(result, true); + }); + + test("enableBluetooth should return false if user-permission not granted", () async { + when(bluetoothService.enableBluetooth()).thenAnswer((_) async => false); + + final bool? result = await bluetoothService.enableBluetooth(); + + verify(bluetoothService.enableBluetooth()).called(1); + expect(result, false); + }); + }); +} From 45583f761f0e51069f4902683ab305c7e1c55d10 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 19:59:40 +0500 Subject: [PATCH 12/14] chore(deps): add `build_runner` dependency for mockito --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 840fc29..db30b85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: firebase_core: ^2.14.0 google_sign_in: ^6.1.4 sign_button: ^2.0.4 + build_runner: ^2.4.6 dev_dependencies: flutter_test: From 501b2dc304021fcd0cff3bd92563b1d7ebbd0287 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 20:00:57 +0500 Subject: [PATCH 13/14] chore(deps): update pubspec.lock --- pubspec.lock | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 2ec60e2..eb4f1fc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_runner: + dependency: "direct main" + description: + name: build_runner + sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" + url: "https://pub.dev" + source: hosted + version: "2.4.6" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + url: "https://pub.dev" + source: hosted + version: "7.2.11" built_collection: dependency: transitive description: @@ -105,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -416,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.0+2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" http: dependency: "direct main" description: @@ -464,6 +520,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" lints: dependency: transitive description: @@ -704,6 +768,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" quiver: dependency: transitive description: @@ -805,6 +877,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -845,6 +925,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.3" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -918,5 +1006,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.5 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" From 0a0a1a5c71913ae3dbff38b20f279f8fa7dc0965 Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Wed, 18 Oct 2023 20:02:45 +0500 Subject: [PATCH 14/14] chore: add generated google-services.json --- android/app/google-services.json | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 android/app/google-services.json diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..717ed5f --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "953137632872", + "firebase_url": "https://smart-link-3680c-default-rtdb.asia-southeast1.firebasedatabase.app", + "project_id": "smart-link-3680c", + "storage_bucket": "smart-link-3680c.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:953137632872:android:826ae8fb3f3bee533d8ddd", + "android_client_info": { + "package_name": "com.mediocre.smartlink" + } + }, + "oauth_client": [ + { + "client_id": "953137632872-f582eq9be8kjoflt0h2kq3udup6pmoud.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.mediocre.smartlink", + "certificate_hash": "75f9e7826ea826bcadd8dfb787dc3c81c9af89f2" + } + }, + { + "client_id": "953137632872-0p65r7e2niub0ktjn2ha650dja0sq0ug.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAr9p9qv7zWawILIQ4TV69Al99wWTj36xg" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "953137632872-0p65r7e2niub0ktjn2ha650dja0sq0ug.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file