From 3145f214d801621588a73cb5116448a22a21409b Mon Sep 17 00:00:00 2001 From: Jhalak Upadhyay <89909502+Jhalakupadhyay@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:24:53 +0530 Subject: [PATCH] feat: Added the BLE state machine to handle BLE state transition gracefully. (#962) Co-authored-by: Aditya Gupta --- assets/vectors/clip_block.SVG | 3 + assets/vectors/clip_clock.SVG | 15 ++ assets/vectors/clip_clock_nine_oclock.SVG | 4 + assets/vectors/clip_clock_six_oclock.SVG | 4 + assets/vectors/clip_clock_twelve_oclock.SVG | 4 + assets/vectors/clip_cross.SVG | 3 + assets/vectors/clip_down_arrow.SVG | 3 + assets/vectors/clip_expressionless_face.SVG | 23 +++ assets/vectors/clip_face.SVG | 24 +++ assets/vectors/clip_flag.SVG | 3 + assets/vectors/clip_heart.SVG | 20 ++ assets/vectors/clip_heart_filled.SVG | 4 + assets/vectors/clip_home.SVG | 4 + assets/vectors/clip_invader.SVG | 48 +++++ assets/vectors/clip_left_arrow.SVG | 3 + assets/vectors/clip_mail.SVG | 51 +++++ assets/vectors/clip_mix1.SVG | 139 +++++++++++++ assets/vectors/clip_mix2.SVG | 181 ++++++++++++++++ assets/vectors/clip_mushroom.SVG | 76 +++++++ assets/vectors/clip_mustache.SVG | 116 +++++++++++ assets/vectors/clip_north_east_arrow.SVG | 3 + assets/vectors/clip_north_west_arrow.SVG | 3 + assets/vectors/clip_oneup.SVG | 65 ++++++ assets/vectors/clip_pause.SVG | 3 + assets/vectors/clip_play.SVG | 3 + assets/vectors/clip_play_pause.SVG | 84 ++++++++ assets/vectors/clip_right_arrow.SVG | 3 + assets/vectors/clip_sad_face.SVG | 21 ++ assets/vectors/clip_smile_face.SVG | 21 ++ assets/vectors/clip_south_east_arrow.SVG | 3 + assets/vectors/clip_south_west_arrow.SVG | 3 + assets/vectors/clip_spider.SVG | 135 ++++++++++++ assets/vectors/clip_subdirectory_left.SVG | 3 + assets/vectors/clip_subdirectory_right.SVG | 3 + assets/vectors/clip_sun.SVG | 34 +++ assets/vectors/clip_thumb_down.SVG | 3 + assets/vectors/clip_thumb_up.SVG | 3 + assets/vectors/clip_thumbs_up.SVG | 53 +++++ assets/vectors/clip_tick.SVG | 3 + assets/vectors/clip_up_arrow.SVG | 3 + assets/vectors/clip_without_mouth.SVG | 18 ++ .../bluetooth/base_ble_state.dart | 63 ++++++ lib/bademagic_module/bluetooth/bluetooth.dart | 130 ------------ .../bluetooth/completed_state.dart | 18 ++ .../bluetooth/connect_state.dart | 38 ++++ .../bluetooth/datagenerator.dart | 22 ++ .../bluetooth/scan_state.dart | 67 ++++++ .../bluetooth/write_state.dart | 53 +++++ lib/bademagic_module/utils/toast_utils.dart | 47 +++++ lib/main.dart | 33 +-- lib/providers/badge_message_provider.dart | 69 +++++-- lib/providers/cardsprovider.dart | 10 + lib/providers/getitlocator.dart | 8 + lib/view/homescreen.dart | 195 ++++++------------ lib/view/widgets/animation_container.dart | 39 ++-- lib/view/widgets/effects_container.dart | 37 ++-- lib/view/widgets/speedial.dart | 55 ++--- pubspec.lock | 49 ++--- pubspec.yaml | 3 +- test/byte_array_utils_test.dart | 51 +++++ test/converters_test.dart | 29 +++ test/temp_test.dart | 7 - test_integration/screenshots.dart | 1 - test_integration/test_driver.dart | 1 - 64 files changed, 1839 insertions(+), 384 deletions(-) create mode 100644 assets/vectors/clip_block.SVG create mode 100644 assets/vectors/clip_clock.SVG create mode 100644 assets/vectors/clip_clock_nine_oclock.SVG create mode 100644 assets/vectors/clip_clock_six_oclock.SVG create mode 100644 assets/vectors/clip_clock_twelve_oclock.SVG create mode 100644 assets/vectors/clip_cross.SVG create mode 100644 assets/vectors/clip_down_arrow.SVG create mode 100644 assets/vectors/clip_expressionless_face.SVG create mode 100644 assets/vectors/clip_face.SVG create mode 100644 assets/vectors/clip_flag.SVG create mode 100644 assets/vectors/clip_heart.SVG create mode 100644 assets/vectors/clip_heart_filled.SVG create mode 100644 assets/vectors/clip_home.SVG create mode 100644 assets/vectors/clip_invader.SVG create mode 100644 assets/vectors/clip_left_arrow.SVG create mode 100644 assets/vectors/clip_mail.SVG create mode 100644 assets/vectors/clip_mix1.SVG create mode 100644 assets/vectors/clip_mix2.SVG create mode 100644 assets/vectors/clip_mushroom.SVG create mode 100644 assets/vectors/clip_mustache.SVG create mode 100644 assets/vectors/clip_north_east_arrow.SVG create mode 100644 assets/vectors/clip_north_west_arrow.SVG create mode 100644 assets/vectors/clip_oneup.SVG create mode 100644 assets/vectors/clip_pause.SVG create mode 100644 assets/vectors/clip_play.SVG create mode 100644 assets/vectors/clip_play_pause.SVG create mode 100644 assets/vectors/clip_right_arrow.SVG create mode 100644 assets/vectors/clip_sad_face.SVG create mode 100644 assets/vectors/clip_smile_face.SVG create mode 100644 assets/vectors/clip_south_east_arrow.SVG create mode 100644 assets/vectors/clip_south_west_arrow.SVG create mode 100644 assets/vectors/clip_spider.SVG create mode 100644 assets/vectors/clip_subdirectory_left.SVG create mode 100644 assets/vectors/clip_subdirectory_right.SVG create mode 100644 assets/vectors/clip_sun.SVG create mode 100644 assets/vectors/clip_thumb_down.SVG create mode 100644 assets/vectors/clip_thumb_up.SVG create mode 100644 assets/vectors/clip_thumbs_up.SVG create mode 100644 assets/vectors/clip_tick.SVG create mode 100644 assets/vectors/clip_up_arrow.SVG create mode 100644 assets/vectors/clip_without_mouth.SVG create mode 100644 lib/bademagic_module/bluetooth/base_ble_state.dart delete mode 100644 lib/bademagic_module/bluetooth/bluetooth.dart create mode 100644 lib/bademagic_module/bluetooth/completed_state.dart create mode 100644 lib/bademagic_module/bluetooth/connect_state.dart create mode 100644 lib/bademagic_module/bluetooth/datagenerator.dart create mode 100644 lib/bademagic_module/bluetooth/scan_state.dart create mode 100644 lib/bademagic_module/bluetooth/write_state.dart create mode 100644 lib/bademagic_module/utils/toast_utils.dart create mode 100644 lib/providers/getitlocator.dart create mode 100644 test/byte_array_utils_test.dart create mode 100644 test/converters_test.dart delete mode 100644 test/temp_test.dart diff --git a/assets/vectors/clip_block.SVG b/assets/vectors/clip_block.SVG new file mode 100644 index 000000000..0fb9012b9 --- /dev/null +++ b/assets/vectors/clip_block.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_clock.SVG b/assets/vectors/clip_clock.SVG new file mode 100644 index 000000000..e97c93365 --- /dev/null +++ b/assets/vectors/clip_clock.SVG @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_clock_nine_oclock.SVG b/assets/vectors/clip_clock_nine_oclock.SVG new file mode 100644 index 000000000..c012cafa0 --- /dev/null +++ b/assets/vectors/clip_clock_nine_oclock.SVG @@ -0,0 +1,4 @@ + + + + diff --git a/assets/vectors/clip_clock_six_oclock.SVG b/assets/vectors/clip_clock_six_oclock.SVG new file mode 100644 index 000000000..0ac348980 --- /dev/null +++ b/assets/vectors/clip_clock_six_oclock.SVG @@ -0,0 +1,4 @@ + + + + diff --git a/assets/vectors/clip_clock_twelve_oclock.SVG b/assets/vectors/clip_clock_twelve_oclock.SVG new file mode 100644 index 000000000..95c4e601c --- /dev/null +++ b/assets/vectors/clip_clock_twelve_oclock.SVG @@ -0,0 +1,4 @@ + + + + diff --git a/assets/vectors/clip_cross.SVG b/assets/vectors/clip_cross.SVG new file mode 100644 index 000000000..709c479f6 --- /dev/null +++ b/assets/vectors/clip_cross.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_down_arrow.SVG b/assets/vectors/clip_down_arrow.SVG new file mode 100644 index 000000000..c72627e2a --- /dev/null +++ b/assets/vectors/clip_down_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_expressionless_face.SVG b/assets/vectors/clip_expressionless_face.SVG new file mode 100644 index 000000000..ed1681a55 --- /dev/null +++ b/assets/vectors/clip_expressionless_face.SVG @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_face.SVG b/assets/vectors/clip_face.SVG new file mode 100644 index 000000000..81732ec1c --- /dev/null +++ b/assets/vectors/clip_face.SVG @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_flag.SVG b/assets/vectors/clip_flag.SVG new file mode 100644 index 000000000..90ef236e1 --- /dev/null +++ b/assets/vectors/clip_flag.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_heart.SVG b/assets/vectors/clip_heart.SVG new file mode 100644 index 000000000..4bf8f5bdc --- /dev/null +++ b/assets/vectors/clip_heart.SVG @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_heart_filled.SVG b/assets/vectors/clip_heart_filled.SVG new file mode 100644 index 000000000..c8a886ac3 --- /dev/null +++ b/assets/vectors/clip_heart_filled.SVG @@ -0,0 +1,4 @@ + + + + diff --git a/assets/vectors/clip_home.SVG b/assets/vectors/clip_home.SVG new file mode 100644 index 000000000..26e3edd6d --- /dev/null +++ b/assets/vectors/clip_home.SVG @@ -0,0 +1,4 @@ + + + + diff --git a/assets/vectors/clip_invader.SVG b/assets/vectors/clip_invader.SVG new file mode 100644 index 000000000..bd1fe1e1d --- /dev/null +++ b/assets/vectors/clip_invader.SVG @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_left_arrow.SVG b/assets/vectors/clip_left_arrow.SVG new file mode 100644 index 000000000..6f14388d0 --- /dev/null +++ b/assets/vectors/clip_left_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_mail.SVG b/assets/vectors/clip_mail.SVG new file mode 100644 index 000000000..65e720fac --- /dev/null +++ b/assets/vectors/clip_mail.SVG @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_mix1.SVG b/assets/vectors/clip_mix1.SVG new file mode 100644 index 000000000..38195a2e6 --- /dev/null +++ b/assets/vectors/clip_mix1.SVG @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_mix2.SVG b/assets/vectors/clip_mix2.SVG new file mode 100644 index 000000000..a55fbd79e --- /dev/null +++ b/assets/vectors/clip_mix2.SVG @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_mushroom.SVG b/assets/vectors/clip_mushroom.SVG new file mode 100644 index 000000000..85d9e9587 --- /dev/null +++ b/assets/vectors/clip_mushroom.SVG @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_mustache.SVG b/assets/vectors/clip_mustache.SVG new file mode 100644 index 000000000..92adf14f8 --- /dev/null +++ b/assets/vectors/clip_mustache.SVG @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_north_east_arrow.SVG b/assets/vectors/clip_north_east_arrow.SVG new file mode 100644 index 000000000..c62e1d4a7 --- /dev/null +++ b/assets/vectors/clip_north_east_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_north_west_arrow.SVG b/assets/vectors/clip_north_west_arrow.SVG new file mode 100644 index 000000000..0540b18ae --- /dev/null +++ b/assets/vectors/clip_north_west_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_oneup.SVG b/assets/vectors/clip_oneup.SVG new file mode 100644 index 000000000..883a647d4 --- /dev/null +++ b/assets/vectors/clip_oneup.SVG @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_pause.SVG b/assets/vectors/clip_pause.SVG new file mode 100644 index 000000000..9cd921ccf --- /dev/null +++ b/assets/vectors/clip_pause.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_play.SVG b/assets/vectors/clip_play.SVG new file mode 100644 index 000000000..19efb4751 --- /dev/null +++ b/assets/vectors/clip_play.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_play_pause.SVG b/assets/vectors/clip_play_pause.SVG new file mode 100644 index 000000000..36b3d5575 --- /dev/null +++ b/assets/vectors/clip_play_pause.SVG @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_right_arrow.SVG b/assets/vectors/clip_right_arrow.SVG new file mode 100644 index 000000000..16eaa9ecf --- /dev/null +++ b/assets/vectors/clip_right_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_sad_face.SVG b/assets/vectors/clip_sad_face.SVG new file mode 100644 index 000000000..e0ff15cee --- /dev/null +++ b/assets/vectors/clip_sad_face.SVG @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_smile_face.SVG b/assets/vectors/clip_smile_face.SVG new file mode 100644 index 000000000..0b4dc9c21 --- /dev/null +++ b/assets/vectors/clip_smile_face.SVG @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_south_east_arrow.SVG b/assets/vectors/clip_south_east_arrow.SVG new file mode 100644 index 000000000..7f1c6083f --- /dev/null +++ b/assets/vectors/clip_south_east_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_south_west_arrow.SVG b/assets/vectors/clip_south_west_arrow.SVG new file mode 100644 index 000000000..a76cea7e9 --- /dev/null +++ b/assets/vectors/clip_south_west_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_spider.SVG b/assets/vectors/clip_spider.SVG new file mode 100644 index 000000000..3f700485a --- /dev/null +++ b/assets/vectors/clip_spider.SVG @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_subdirectory_left.SVG b/assets/vectors/clip_subdirectory_left.SVG new file mode 100644 index 000000000..fa53a91e4 --- /dev/null +++ b/assets/vectors/clip_subdirectory_left.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_subdirectory_right.SVG b/assets/vectors/clip_subdirectory_right.SVG new file mode 100644 index 000000000..38c6bf374 --- /dev/null +++ b/assets/vectors/clip_subdirectory_right.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_sun.SVG b/assets/vectors/clip_sun.SVG new file mode 100644 index 000000000..c57f584eb --- /dev/null +++ b/assets/vectors/clip_sun.SVG @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_thumb_down.SVG b/assets/vectors/clip_thumb_down.SVG new file mode 100644 index 000000000..692ca1c67 --- /dev/null +++ b/assets/vectors/clip_thumb_down.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_thumb_up.SVG b/assets/vectors/clip_thumb_up.SVG new file mode 100644 index 000000000..002d37bf1 --- /dev/null +++ b/assets/vectors/clip_thumb_up.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_thumbs_up.SVG b/assets/vectors/clip_thumbs_up.SVG new file mode 100644 index 000000000..6ca968fe3 --- /dev/null +++ b/assets/vectors/clip_thumbs_up.SVG @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/clip_tick.SVG b/assets/vectors/clip_tick.SVG new file mode 100644 index 000000000..fba05fe4a --- /dev/null +++ b/assets/vectors/clip_tick.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_up_arrow.SVG b/assets/vectors/clip_up_arrow.SVG new file mode 100644 index 000000000..3345a247b --- /dev/null +++ b/assets/vectors/clip_up_arrow.SVG @@ -0,0 +1,3 @@ + + + diff --git a/assets/vectors/clip_without_mouth.SVG b/assets/vectors/clip_without_mouth.SVG new file mode 100644 index 000000000..4c9b4a687 --- /dev/null +++ b/assets/vectors/clip_without_mouth.SVG @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/bademagic_module/bluetooth/base_ble_state.dart b/lib/bademagic_module/bluetooth/base_ble_state.dart new file mode 100644 index 000000000..6de7e1c7f --- /dev/null +++ b/lib/bademagic_module/bluetooth/base_ble_state.dart @@ -0,0 +1,63 @@ +import 'package:badgemagic/bademagic_module/bluetooth/completed_state.dart'; +import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; +import 'package:logger/logger.dart'; + +abstract class BleState { + Future process(); +} + +abstract class NormalBleState extends BleState { + final logger = Logger(); + final toast = ToastUtils(); + + Future processState(); + + @override + Future process() async { + try { + return await processState(); + } on Exception catch (e) { + String errorMessage = e.toString().replaceFirst('Exception: ', ''); + return CompletedState(isSuccess: false, message: errorMessage); + } + } +} + +abstract class RetryBleState extends BleState { + final logger = Logger(); + final toast = ToastUtils(); + + final _maxRetries = 3; + + Future processState(); + + @override + Future process() async { + int attempt = 0; + Exception? lastException; + + while (attempt < _maxRetries) { + try { + return await processState(); + } on Exception catch (e) { + logger.e(e); + lastException = e; + attempt++; + if (attempt < _maxRetries) { + logger.d("Retrying ($attempt/$_maxRetries)..."); + await Future.delayed( + const Duration(seconds: 2)); // Wait before retrying + } else { + logger.e("Max retries reached. Last exception: $lastException"); + lastException = + Exception("Max retries reached. Last exception: $lastException"); + } + } + } + + // After max retries, return a CompletedState indicating failure. + return CompletedState( + isSuccess: false, + message: lastException?.toString() ?? "Unknown error"); + } +} diff --git a/lib/bademagic_module/bluetooth/bluetooth.dart b/lib/bademagic_module/bluetooth/bluetooth.dart deleted file mode 100644 index a5fcf0926..000000000 --- a/lib/bademagic_module/bluetooth/bluetooth.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'dart:async'; -import 'package:badgemagic/bademagic_module/models/data.dart'; -import 'package:badgemagic/bademagic_module/utils/data_to_bytearray_converter.dart'; -import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:logger/logger.dart'; - -class BadgeMagicBluetooth { - static final Logger logger = Logger(); - static DataToByteArrayConverter converter = DataToByteArrayConverter(); - - static Future writeCharacteristic( - BluetoothDevice device, - Guid characteristicId, - Data data, - ) async { - List> dataChunks = converter.convert(data); - logger.d("Data to write: $dataChunks"); - - try { - List services = await device.discoverServices(); - for (BluetoothService service in services) { - for (BluetoothCharacteristic characteristic - in service.characteristics) { - if (characteristic.uuid == characteristicId && - characteristic.properties.write) { - for (int attempt = 1; attempt <= 3; attempt++) { - for (List chunk in dataChunks) { - bool success = false; - try { - await characteristic.write(chunk, withoutResponse: false); - await Future.delayed(const Duration( - milliseconds: 100)); // Add a delay between writes - success = true; - } catch (e) { - logger.e("Write failed, retrying ($attempt/3): $e"); - } - if (!success) { - throw Exception( - "Failed to write chunk after 3 attempts: $chunk"); - } - } - } - logger.d("Characteristic written successfully"); - return; // Exit once the target characteristic is written - } - } - } - logger.d("Target characteristic not found"); - } catch (e) { - logger.e("Failed to write characteristic: $e"); - } - } - - static Future scanAndConnect(Data data) async { - ScanResult? foundDevice; - - StreamSubscription>? subscription; - - try { - subscription = FlutterBluePlus.scanResults.listen( - (results) async { - if (results.isNotEmpty) { - foundDevice = results.firstWhere( - (result) => result.advertisementData.serviceUuids - .contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb")), - ); - if (foundDevice != null) { - await connectToDevice(foundDevice!, data); - } else { - logger.e("Target device not found."); - } - } - }, - onError: (e) { - logger.e("Scan error: $e"); - }, - ); - - await FlutterBluePlus.startScan( - withServices: [Guid("0000fee0-0000-1000-8000-00805f9b34fb")], - timeout: const Duration(seconds: 10), - ); - - // Wait for the scan to complete before cancelling the subscription - await Future.delayed(const Duration(seconds: 11)); - } finally { - await subscription?.cancel(); - } - } - - static Future connectToDevice(ScanResult scanResult, Data data) async { - const int maxRetries = 3; - int attempt = 0; - bool connected = false; - - while (attempt < maxRetries && !connected) { - try { - await scanResult.device.connect(autoConnect: false); - BluetoothConnectionState connectionState = - await scanResult.device.connectionState.first; - - if (connectionState == BluetoothConnectionState.connected) { - logger.d("Device connected"); - await writeCharacteristic( - scanResult.device, - Guid("0000fee1-0000-1000-8000-00805f9b34fb"), - data, - ); - connected = true; - } else { - logger.e("Failed to connect to the device"); - } - } catch (e) { - logger.e("Connection error: $e"); - attempt++; - if (attempt < maxRetries) { - logger.d("Retrying connection ($attempt/$maxRetries)..."); - await Future.delayed( - const Duration(seconds: 2)); // Wait before retrying - } else { - logger.e("Max retries reached. Connection failed."); - } - } finally { - if (!connected) { - await scanResult.device.disconnect(); - } - } - } - } -} diff --git a/lib/bademagic_module/bluetooth/completed_state.dart b/lib/bademagic_module/bluetooth/completed_state.dart new file mode 100644 index 000000000..2e2df4084 --- /dev/null +++ b/lib/bademagic_module/bluetooth/completed_state.dart @@ -0,0 +1,18 @@ +import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart'; + +class CompletedState extends NormalBleState { + final bool isSuccess; + final String message; + + CompletedState({required this.isSuccess, required this.message}); + + @override + Future processState() async { + if (isSuccess) { + toast.showToast(message); + } else { + toast.showErrorToast(message); + } + return null; + } +} diff --git a/lib/bademagic_module/bluetooth/connect_state.dart b/lib/bademagic_module/bluetooth/connect_state.dart new file mode 100644 index 000000000..0b67e2f7c --- /dev/null +++ b/lib/bademagic_module/bluetooth/connect_state.dart @@ -0,0 +1,38 @@ +import 'package:badgemagic/bademagic_module/bluetooth/write_state.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'base_ble_state.dart'; + +class ConnectState extends RetryBleState { + final ScanResult scanResult; + + ConnectState({required this.scanResult}); + + @override + Future processState() async { + bool connected = false; + + try { + await scanResult.device.connect(autoConnect: false); + BluetoothConnectionState connectionState = + await scanResult.device.connectionState.first; + + if (connectionState == BluetoothConnectionState.connected) { + connected = true; + + logger.d("Device connected"); + toast.showToast('Device connected successfully.'); + + return WriteState(device: scanResult.device); + } else { + throw Exception("Failed to connect to the device"); + } + } catch (e) { + toast.showErrorToast('Failed to connect retrying...'); + rethrow; + } finally { + if (!connected) { + await scanResult.device.disconnect(); + } + } + } +} diff --git a/lib/bademagic_module/bluetooth/datagenerator.dart b/lib/bademagic_module/bluetooth/datagenerator.dart new file mode 100644 index 000000000..e794bccc9 --- /dev/null +++ b/lib/bademagic_module/bluetooth/datagenerator.dart @@ -0,0 +1,22 @@ +import 'package:badgemagic/bademagic_module/models/data.dart'; +import 'package:badgemagic/bademagic_module/utils/data_to_bytearray_converter.dart'; +import 'package:badgemagic/providers/badge_message_provider.dart'; +import 'package:badgemagic/providers/cardsprovider.dart'; +import 'package:get_it/get_it.dart'; + +class DataTransferManager { + final BadgeMessageProvider badgeData = BadgeMessageProvider(); + DataToByteArrayConverter converter = DataToByteArrayConverter(); + CardProvider cardData = GetIt.instance(); + + List> generateDataChunk() { + Data data = badgeData.generateData( + cardData.getController().text, + cardData.getEffectIndex(1) == 1, + cardData.getEffectIndex(2) == 1, + badgeData.speedMap[cardData.getOuterValue()]!, + badgeData.modeValueMap[cardData.getAnimationIndex()]!, + ); + return converter.convert(data); + } +} diff --git a/lib/bademagic_module/bluetooth/scan_state.dart b/lib/bademagic_module/bluetooth/scan_state.dart new file mode 100644 index 000000000..b49e7f536 --- /dev/null +++ b/lib/bademagic_module/bluetooth/scan_state.dart @@ -0,0 +1,67 @@ +import 'dart:async'; +import 'package:badgemagic/bademagic_module/bluetooth/connect_state.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; + +import 'base_ble_state.dart'; + +class ScanState extends NormalBleState { + @override + Future processState() async { + StreamSubscription>? subscription; + toast.showToast("Searching for device..."); + + Completer nextStateCompleter = Completer(); + bool isCompleted = false; + + ScanResult? foundDevice; + + try { + subscription = FlutterBluePlus.scanResults.listen( + (results) async { + if (!isCompleted) { + if (results.isNotEmpty) { + foundDevice = results.firstWhere( + (result) => result.advertisementData.serviceUuids + .contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb")), + ); + if (foundDevice != null) { + toast.showToast('Device found. Connecting...'); + isCompleted = true; + nextStateCompleter + .complete(ConnectState(scanResult: foundDevice!)); + } + } + } + }, + onError: (e) async { + if (!isCompleted) { + isCompleted = true; + logger.e("Scan error: $e"); + toast.showErrorToast('Scan error occurred.'); + nextStateCompleter.completeError(e); + } + }, + ); + + await FlutterBluePlus.startScan( + withServices: [Guid("0000fee0-0000-1000-8000-00805f9b34fb")], + timeout: const Duration(seconds: 15), // Reduced scan timeout + ); + + await Future.delayed(const Duration(seconds: 1)); + + // If no device is found after the scan timeout, complete with an error. + if (!isCompleted) { + toast.showToast('Device not found.'); + nextStateCompleter.completeError(Exception('Device not found.')); + } + + return await nextStateCompleter.future; + } catch (e) { + logger.e("Exception during scanning: $e"); + throw Exception("please check the device is turned on and retry."); + } finally { + await subscription?.cancel(); + } + } +} diff --git a/lib/bademagic_module/bluetooth/write_state.dart b/lib/bademagic_module/bluetooth/write_state.dart new file mode 100644 index 000000000..3df3ad71c --- /dev/null +++ b/lib/bademagic_module/bluetooth/write_state.dart @@ -0,0 +1,53 @@ +import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'base_ble_state.dart'; +import 'completed_state.dart'; + +class WriteState extends NormalBleState { + final BluetoothDevice device; + + DataTransferManager manager = DataTransferManager(); + + WriteState({required this.device}); + + @override + Future processState() async { + List> dataChunks = manager.generateDataChunk(); + logger.d("Data to write: $dataChunks"); + + try { + List services = await device.discoverServices(); + for (BluetoothService service in services) { + for (BluetoothCharacteristic characteristic + in service.characteristics) { + if (characteristic.uuid == + Guid("0000fee1-0000-1000-8000-00805f9b34fb") && + characteristic.properties.write) { + for (List chunk in dataChunks) { + bool success = false; + for (int attempt = 1; attempt <= 3; attempt++) { + try { + await characteristic.write(chunk, withoutResponse: false); + success = true; + break; + } catch (e) { + logger.e("Write failed, retrying ($attempt/3): $e"); + } + } + if (!success) { + throw Exception("Failed to transfer data. Please try again."); + } + } + logger.d("Characteristic written successfully"); + return CompletedState( + isSuccess: true, message: "Data transferred successfully"); + } + } + } + throw Exception("Please use the correct Badge"); + } catch (e) { + logger.e("Failed to write characteristic: $e"); + throw Exception("Failed to transfer data. Please try again."); + } + } +} diff --git a/lib/bademagic_module/utils/toast_utils.dart b/lib/bademagic_module/utils/toast_utils.dart new file mode 100644 index 000000000..7fcc9606b --- /dev/null +++ b/lib/bademagic_module/utils/toast_utils.dart @@ -0,0 +1,47 @@ +import 'package:badgemagic/providers/cardsprovider.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; + +class ToastUtils { + CardProvider contextProvider = GetIt.instance(); + + // Create a toast message + void showToast(String message) { + ScaffoldMessenger.of(contextProvider.getContext()!).showSnackBar( + SnackBar( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + elevation: 10, + duration: const Duration(seconds: 1), + content: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Image( + image: AssetImage('assets/icons/icon.png'), + height: 20, + ), + const SizedBox( + width: 10, + ), + Flexible( + child: Text( + message, + style: const TextStyle(color: Colors.black), + ), + ) + ], + ), + backgroundColor: Colors.white, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + dismissDirection: DismissDirection.startToEnd, + ), + ); + } + + // Create a error toast + void showErrorToast(String message) { + showToast('Error: $message'); + } +} diff --git a/lib/main.dart b/lib/main.dart index 19381559e..a6627e4cd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,17 @@ -import 'package:badgemagic/providers/badge_message_provider.dart'; +import 'package:badgemagic/providers/getitlocator.dart'; import 'package:badgemagic/view/homescreen.dart'; import 'package:badgemagic/view/splashscreen.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:badgemagic/providers/cardsprovider.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; void main() { + setupLocator(); runApp(MultiProvider( providers: [ - ChangeNotifierProvider(create: (context) => CardProvider()), - ChangeNotifierProvider(create: (context) => BadgeMessageProvider()), + ChangeNotifierProvider( + create: (context) => getIt()), ], child: const MyApp(), )); @@ -20,16 +22,21 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.red), - useMaterial3: true, - ), - initialRoute: '/', - routes: { - '/': (context) => const SpalshScreen(), - '/homescreen': (context) => const HomeScreen(), + return ScreenUtilInit( + designSize: const Size(360, 690), + builder: (context, child) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.red), + useMaterial3: true, + ), + initialRoute: '/', + routes: { + '/': (context) => const SpalshScreen(), + '/homescreen': (context) => const HomeScreen(), + }, + ); }, ); } diff --git a/lib/providers/badge_message_provider.dart b/lib/providers/badge_message_provider.dart index e8f83574c..cc6f1ce41 100644 --- a/lib/providers/badge_message_provider.dart +++ b/lib/providers/badge_message_provider.dart @@ -1,15 +1,21 @@ -import 'package:badgemagic/bademagic_module/bluetooth/bluetooth.dart'; +import 'dart:io'; +import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart'; +import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; +import 'package:badgemagic/bademagic_module/bluetooth/scan_state.dart'; import 'package:badgemagic/bademagic_module/models/data.dart'; import 'package:badgemagic/bademagic_module/models/messages.dart'; import 'package:badgemagic/bademagic_module/models/mode.dart'; import 'package:badgemagic/bademagic_module/models/speed.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; -import 'package:flutter/material.dart'; +import 'package:badgemagic/providers/cardsprovider.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:get_it/get_it.dart'; import 'package:logger/logger.dart'; -class BadgeMessageProvider extends ChangeNotifier { - BadgeMagicBluetooth badgeMagicBluetooth = BadgeMagicBluetooth(); +class BadgeMessageProvider { static final Logger logger = Logger(); + CardProvider cardData = GetIt.instance(); + ToastUtils toast = ToastUtils(); Map modeValueMap = { 0: Mode.left, @@ -34,25 +40,54 @@ class BadgeMessageProvider extends ChangeNotifier { 8: Speed.eight, }; - void generateMessage( + Data generateData( String text, bool flash, bool marq, Speed speed, Mode mode) { Data data = Data(messages: [ Message( - text: Converters.messageTohex(text), - flash: flash, - marquee: marq, - speed: speed, - mode: mode) + text: Converters.messageTohex(text), + flash: flash, + marquee: marq, + speed: speed, + mode: mode, + ) ]); - dataFormed(data); - transferData(data); + logger.d( + "${data.messages.length} message : ${data.messages[0].text} Flash : ${data.messages[0].flash} Marquee : ${data.messages[0].marquee} Mode : ${data.messages[0].mode}"); + return data; } - void transferData(Data data) { - BadgeMagicBluetooth.scanAndConnect(data); - logger.d(".......Data is being transferred......."); + Future transferData() async { + DateTime now = DateTime.now(); + BleState? state = ScanState(); + while (state != null) { + state = await state.process(); + } + + logger.d("Time to transfer data is = ${DateTime.now().difference(now)}"); + logger.d(".......Data transfer completed......."); } - void dataFormed(Data data) => logger.d( - "${data.messages.length} message : ${data.messages[0].text} Flash : ${data.messages[0].flash} Marquee : ${data.messages[0].marquee} Mode : ${data.messages[0].mode}"); + Future checkAndTransfer() async { + if (await FlutterBluePlus.isSupported == false) { + toast.showErrorToast('Bluetooth is not supported by the device'); + return; + } + + if (cardData.getController().text.isEmpty) { + toast.showErrorToast("Please enter a message"); + return; + } + + final adapterState = await FlutterBluePlus.adapterState.first; + if (adapterState == BluetoothAdapterState.on) { + await transferData(); + } else { + if (Platform.isAndroid) { + toast.showToast('Turning on Bluetooth...'); + await FlutterBluePlus.turnOn(); + } else if (Platform.isIOS) { + toast.showToast('Please turn on Bluetooth'); + } + } + } } diff --git a/lib/providers/cardsprovider.dart b/lib/providers/cardsprovider.dart index 2717d4a6e..56fc91dcc 100644 --- a/lib/providers/cardsprovider.dart +++ b/lib/providers/cardsprovider.dart @@ -5,6 +5,16 @@ class CardProvider extends ChangeNotifier { int getOuterValue() => outerValue; + //context for snackbar + BuildContext? context; + + void setContext(BuildContext context) { + this.context = context; + notifyListeners(); + } + + BuildContext? getContext() => context; + void setOuterValue(int value) { outerValue = value; notifyListeners(); diff --git a/lib/providers/getitlocator.dart b/lib/providers/getitlocator.dart new file mode 100644 index 000000000..4c7757208 --- /dev/null +++ b/lib/providers/getitlocator.dart @@ -0,0 +1,8 @@ +import 'package:badgemagic/providers/cardsprovider.dart'; +import 'package:get_it/get_it.dart'; + +final GetIt getIt = GetIt.instance; + +void setupLocator() { + getIt.registerLazySingleton(() => CardProvider()); +} diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index 61d12a712..f43b2e742 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -5,8 +5,8 @@ import 'package:badgemagic/view/widgets/homescreentabs.dart'; import 'package:badgemagic/view/widgets/speedial.dart'; import 'package:badgemagic/virtualbadge/view/badgeui.dart'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -25,50 +25,13 @@ class _HomeScreenState extends State with TickerProviderStateMixin { _tabController = TabController(length: 3, vsync: this); } - Future transferData(BuildContext context, CardProvider cardData) async { - Fluttertoast.showToast( - msg: "Transferring...", - toastLength: Toast.LENGTH_LONG, - gravity: ToastGravity.BOTTOM, - backgroundColor: Colors.black, - textColor: Colors.white, - fontSize: 16.0, - ); - - try { - badgeData.generateMessage( - cardData.getController().text, - cardData.getEffectIndex(1) == 1, - cardData.getEffectIndex(2) == 1, - badgeData.speedMap[cardData.getOuterValue()]!, - badgeData.modeValueMap[cardData.getAnimationIndex()]!, - ); - - Fluttertoast.showToast( - msg: "Transfer successful", - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - backgroundColor: Colors.green, - textColor: Colors.white, - fontSize: 16.0, - ); - } catch (e) { - Fluttertoast.showToast( - msg: "Transfer failed: $e", - toastLength: Toast.LENGTH_LONG, - gravity: ToastGravity.CENTER, - backgroundColor: Colors.red, - textColor: Colors.white, - fontSize: 16.0, - ); - } - } - @override Widget build(BuildContext context) { - double height = MediaQuery.of(context).size.height; - double width = MediaQuery.of(context).size.width; CardProvider cardData = Provider.of(context); + WidgetsBinding.instance.addPostFrameCallback((_) { + cardData.setContext(context); + }); + return DefaultTabController( length: 3, child: Scaffold( @@ -82,96 +45,72 @@ class _HomeScreenState extends State with TickerProviderStateMixin { centerTitle: true, ), body: SafeArea( - child: SizedBox( - height: height, - width: width, - child: SingleChildScrollView( - child: Column( - children: [ - const BMBadge(), - Container( - margin: const EdgeInsets.all(15), - child: Material( - borderRadius: BorderRadius.circular(10), - elevation: 10, - child: TextField( - controller: cardData.getController(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10)), - prefixIcon: const Icon(Icons.tag_faces_outlined), - focusedBorder: const OutlineInputBorder( - borderSide: BorderSide(color: Colors.red))), - ), - ), - ), - TabBar( - indicatorSize: TabBarIndicatorSize.label, - controller: _tabController, - tabs: const [ - Tab(text: 'Speed'), - Tab(text: 'Animation'), - Tab(text: 'Effects'), - ], + child: SingleChildScrollView( + child: Column( + children: [ + const BMBadge(), + Container( + margin: EdgeInsets.all(15.w), + child: Material( + borderRadius: BorderRadius.circular(10.r), + elevation: 10, + child: TextField( + controller: cardData.getController(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r)), + prefixIcon: const Icon(Icons.tag_faces_outlined), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.red))), ), - SingleChildScrollView( - child: Column( - children: [ - AspectRatio( - aspectRatio: 1.5, - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - controller: _tabController, - children: const [ - RadialDial(), - AnimationTab(), - EffectTab(), - ], - ), - ), - Container( - padding: EdgeInsets.only( - bottom: height * 0.2, - top: height * - 0.02), // Adjust the value as needed - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - if (cardData.getController().text.isEmpty) { - Fluttertoast.showToast( - msg: - "Please enter some text to transfer.", - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - backgroundColor: Colors.red, - textColor: Colors.white, - fontSize: 16.0, - ); - return; - } - transferData(context, cardData); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.grey.shade400, - ), - child: const Text('Transfer'), - ), - ), - ], - ), + ), + ), + TabBar( + indicatorSize: TabBarIndicatorSize.label, + controller: _tabController, + tabs: const [ + Tab(text: 'Speed'), + Tab(text: 'Animation'), + Tab(text: 'Effects'), + ], + ), + SizedBox( + height: 230.h, // Adjust the height dynamically + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, + children: const [ + RadialDial(), + AnimationTab(), + EffectTab(), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 20.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + badgeData.checkAndTransfer(); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Colors.grey.shade400, ), - ], + child: const Text('Transfer'), + ), ), - ), - ], + ], + ), ), - )), + ], + ), + ), ), ), ); diff --git a/lib/view/widgets/animation_container.dart b/lib/view/widgets/animation_container.dart index cf8bd03ce..1acde20fe 100644 --- a/lib/view/widgets/animation_container.dart +++ b/lib/view/widgets/animation_container.dart @@ -1,48 +1,49 @@ -import 'package:badgemagic/providers/cardsprovider.dart'; import 'package:flutter/material.dart'; +import 'package:badgemagic/providers/cardsprovider.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; -//This the container that holds the UI appearence of the animation and effects Tabs class AniContainer extends StatefulWidget { final String animation; final String aniName; final int index; - const AniContainer( - {super.key, - required this.animation, - required this.aniName, - required this.index}); + + const AniContainer({ + super.key, + required this.animation, + required this.aniName, + required this.index, + }); @override State createState() => _AniContainerState(); } class _AniContainerState extends State { - Color tintcolor = Colors.white; @override Widget build(BuildContext context) { - CardProvider animationcardstate = Provider.of(context); - double height = MediaQuery.of(context).size.height; - double width = MediaQuery.of(context).size.width; + CardProvider animationCardState = Provider.of(context); + return Container( - margin: const EdgeInsets.all(5), - height: height * 0.09, - width: width * 0.307, + margin: EdgeInsets.all(5.w), + height: 60.h, + width: 110.w, child: GestureDetector( onTap: () { - animationcardstate.setAnimationIndex(widget.index); + animationCardState.setAnimationIndex(widget.index); }, child: Card( surfaceTintColor: Colors.white, - color: animationcardstate.getAnimationIndex() == widget.index + color: animationCardState.getAnimationIndex() == widget.index ? Colors.red : Colors.white, elevation: 5, child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Image( - image: AssetImage(widget.animation), - height: height * 0.04, + Image.asset( + widget.animation, + height: 20.h, ), Text(widget.aniName), ], diff --git a/lib/view/widgets/effects_container.dart b/lib/view/widgets/effects_container.dart index beaa52f67..92d0b1361 100644 --- a/lib/view/widgets/effects_container.dart +++ b/lib/view/widgets/effects_container.dart @@ -1,16 +1,19 @@ -import 'package:badgemagic/providers/cardsprovider.dart'; import 'package:flutter/material.dart'; +import 'package:badgemagic/providers/cardsprovider.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; class EffectContainer extends StatefulWidget { final String effect; final String effectName; final int index; - const EffectContainer( - {super.key, - required this.effect, - required this.effectName, - required this.index}); + + const EffectContainer({ + super.key, + required this.effect, + required this.effectName, + required this.index, + }); @override State createState() => _EffectContainerState(); @@ -19,28 +22,28 @@ class EffectContainer extends StatefulWidget { class _EffectContainerState extends State { @override Widget build(BuildContext context) { - CardProvider effectcardstate = Provider.of(context); - double height = MediaQuery.of(context).size.height; - double width = MediaQuery.of(context).size.width; + CardProvider effectCardState = Provider.of(context); + return Container( - margin: const EdgeInsets.all(5), - height: height * 0.15, - width: width * 0.307, + margin: EdgeInsets.all(5.w), + height: 90.h, + width: 110.w, child: GestureDetector( onTap: () { - effectcardstate.setEffectIndex(widget.index); + effectCardState.setEffectIndex(widget.index); }, child: Card( surfaceTintColor: Colors.white, - color: effectcardstate.getEffectIndex(widget.index) == 1 + color: effectCardState.getEffectIndex(widget.index) == 1 ? Colors.red : Colors.white, elevation: 5, child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Image( - image: AssetImage(widget.effect), - height: height * 0.1, + Image.asset( + widget.effect, + height: 60.h, ), Text(widget.effectName), ], diff --git a/lib/view/widgets/speedial.dart b/lib/view/widgets/speedial.dart index 20ab62cae..a2b9732ca 100644 --- a/lib/view/widgets/speedial.dart +++ b/lib/view/widgets/speedial.dart @@ -1,8 +1,8 @@ -import 'package:badgemagic/providers/cardsprovider.dart'; import 'package:flutter/material.dart'; -import 'dart:math'; - +import 'package:badgemagic/providers/cardsprovider.dart'; import 'package:provider/provider.dart'; +import 'dart:math'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; class InnerDialPainter extends CustomPainter { @override @@ -13,7 +13,7 @@ class InnerDialPainter extends CustomPainter { final paint = Paint() ..color = Colors.grey.shade300 ..style = PaintingStyle.stroke - ..strokeWidth = 12; + ..strokeWidth = 12.w; canvas.drawCircle(center, radius, paint); } @@ -44,7 +44,7 @@ class RadialDialPainter extends CustomPainter { ..color = Colors.grey.shade300 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round - ..strokeWidth = 15; + ..strokeWidth = 12.w; const startAngle = 3 * pi / 4; @@ -60,7 +60,7 @@ class RadialDialPainter extends CustomPainter { ..color = Colors.red ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round - ..strokeWidth = 15; + ..strokeWidth = 12.w; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), @@ -94,12 +94,12 @@ class InnerPointerPainter extends CustomPainter { final radius = min(size.width / 2, size.height / 2) * 0.4; final pointerAngle = 3 * pi / 4 + 6 * pi / 4 * (value / max); - final pointerLength = radius + 25; + final pointerLength = radius + 25.w; final pointerPaint = Paint() ..color = color ..strokeCap = StrokeCap.round - ..strokeWidth = 4; + ..strokeWidth = 4.w; final pointerStart = Offset( center.dx + radius * cos(pointerAngle), @@ -138,7 +138,6 @@ class _RadialDialState extends State { @override void initState() { super.initState(); - previousAngle = initialAngle; } @@ -201,20 +200,20 @@ class _RadialDialState extends State { children: [ CustomPaint( painter: RadialDialPainter( - value: outerValueProvider.getOuterValue().toDouble(), - max: maxValue, - color: Colors.red), - child: const SizedBox( - width: 250, - height: 250, + value: outerValueProvider.getOuterValue().toDouble(), + max: maxValue, + color: Colors.red, + ), + child: SizedBox( + width: 230.w, + height: 250.h, ), ), CustomPaint( painter: InnerDialPainter(), child: Container( color: Colors.transparent, - width: 170, - height: 170, + width: 150.w, ), ), GestureDetector( @@ -228,22 +227,24 @@ class _RadialDialState extends State { }, child: CustomPaint( painter: InnerPointerPainter( - value: outerValueProvider.getOuterValue().toDouble(), - max: maxValue, - color: Colors.red), - child: const SizedBox( - width: 180, - height: 180, + value: outerValueProvider.getOuterValue().toDouble(), + max: maxValue, + color: Colors.red, + ), + child: SizedBox( + width: 140.w, + height: 140.h, ), ), ), Positioned( child: Text( (outerValueProvider.getOuterValue()).toString(), - style: const TextStyle( - fontSize: 60, - fontWeight: FontWeight.w600, - color: Color.fromRGBO(113, 113, 113, 1)), + style: TextStyle( + fontSize: 60.sp, + fontWeight: FontWeight.w600, + color: const Color.fromRGBO(113, 113, 113, 1), + ), ), ), ], diff --git a/pubspec.lock b/pubspec.lock index 9728a1139..e1bdc984d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -139,6 +139,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" + url: "https://pub.dev" + source: hosted + version: "5.9.3" flutter_svg: dependency: "direct main" description: @@ -152,24 +160,19 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_web_plugins: + fuchsia_remote_debug_protocol: dependency: transitive description: flutter source: sdk version: "0.0.0" - fluttertoast: + get_it: dependency: "direct main" description: - name: fluttertoast - sha256: "7eae679e596a44fdf761853a706f74979f8dd3cd92cf4e23cae161fda091b847" + name: get_it + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 url: "https://pub.dev" source: hosted - version: "8.2.6" - fuchsia_remote_debug_protocol: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" + version: "7.7.0" glob: dependency: transitive description: @@ -203,26 +206,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -259,10 +262,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" nested: dependency: transitive description: @@ -392,10 +395,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -440,10 +443,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" watcher: dependency: transitive description: @@ -486,4 +489,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8689a46aa..deb99dbba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,9 +37,10 @@ dependencies: cupertino_icons: ^1.0.6 provider: ^6.1.2 flutter_blue_plus: ^1.32.7 - fluttertoast: ^8.2.6 logger: ^2.3.0 flutter_svg: ^2.0.10+1 + get_it: ^7.7.0 + flutter_screenutil: ^5.9.3 dev_dependencies: flutter_test: diff --git a/test/byte_array_utils_test.dart b/test/byte_array_utils_test.dart new file mode 100644 index 000000000..4fa7625f5 --- /dev/null +++ b/test/byte_array_utils_test.dart @@ -0,0 +1,51 @@ +import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ByteArrayUtils', () { + test('toHex should convert bytes to a hexadecimal string', () { + final bytes = [10, 20, 30, 40]; + const expectedHex = '0A141E28'; + + final result = toHex(bytes); + + expect(result, equals(expectedHex)); + }); + + test('isValidHex should return true for a valid hexadecimal string', () { + const validHex = '0A141E28'; + + final result = isValidHex(validHex); + + expect(result, isTrue); + }); + + test('isValidHex should return false for an invalid hexadecimal string', + () { + const invalidHex = 'GHIJKL'; + + final result = isValidHex(invalidHex); + + expect(result, isFalse); + }); + + test( + 'hexStringToByteArray should convert a valid hexadecimal string to a byte array', + () { + const hexString = '0A141E28'; + final expectedBytes = [10, 20, 30, 40]; + + final result = hexStringToByteArray(hexString); + + expect(result, equals(expectedBytes)); + }); + + test( + 'hexStringToByteArray should throw an ArgumentError for an invalid hexadecimal string', + () { + const invalidHexString = 'GHIJKL'; + + expect(() => hexStringToByteArray(invalidHexString), throwsArgumentError); + }); + }); +} diff --git a/test/converters_test.dart b/test/converters_test.dart new file mode 100644 index 000000000..f1b6dcac6 --- /dev/null +++ b/test/converters_test.dart @@ -0,0 +1,29 @@ +import 'package:badgemagic/bademagic_module/utils/converters.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test( + 'Message to hex function should be able to generate the hex with skipping invalid characters', + () { + const String message = "Hii!"; + List result = Converters.messageTohex(message); + List expected = [ + "00C6C6C6C6FEC6C6C6C600", + "0018180038181818183C00", + "0018180038181818183C00", + "00183C3C3C181800181800" + ]; + expect(result, expected); + }); + + test('Converts a simple 2x2 bitmap to LED hex', () { + List> image = [ + [1, 0], + [0, 1] + ]; + + List result = Converters.convertBitmapToLEDHex(image); + + expect(result, ["1008"]); + }); +} diff --git a/test/temp_test.dart b/test/temp_test.dart deleted file mode 100644 index 979a79910..000000000 --- a/test/temp_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('Temporary Test', () { - expect(0, 0); - }); -} diff --git a/test_integration/screenshots.dart b/test_integration/screenshots.dart index 6f2e32df0..0b7aab89b 100644 --- a/test_integration/screenshots.dart +++ b/test_integration/screenshots.dart @@ -1,5 +1,4 @@ import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/test_integration/test_driver.dart b/test_integration/test_driver.dart index 96a2af453..a46844ed9 100644 --- a/test_integration/test_driver.dart +++ b/test_integration/test_driver.dart @@ -1,7 +1,6 @@ // ignore_for_file: avoid_print import 'dart:io'; - import 'package:integration_test/integration_test_driver_extended.dart'; Future main() async {