diff --git a/lib/bademagic_module/bluetooth/scan_state.dart b/lib/bademagic_module/bluetooth/scan_state.dart index 04c2a02bf..e24bf6e81 100644 --- a/lib/bademagic_module/bluetooth/scan_state.dart +++ b/lib/bademagic_module/bluetooth/scan_state.dart @@ -1,13 +1,20 @@ import 'dart:async'; import 'package:badgemagic/bademagic_module/bluetooth/connect_state.dart'; import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart'; +import 'package:badgemagic/providers/BadgeScanProvider.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'base_ble_state.dart'; class ScanState extends NormalBleState { final DataTransferManager manager; + final BadgeScanMode mode; + final List allowedNames; - ScanState({required this.manager}); + ScanState({ + required this.manager, + required this.mode, + required this.allowedNames, + }); @override Future processState() async { @@ -25,21 +32,35 @@ class ScanState extends NormalBleState { if (isCompleted || results.isEmpty) return; try { + final normalizedAllowedNames = allowedNames + .map((e) => e.trim().toLowerCase()) + .where((e) => e.isNotEmpty) + .toList(); + final foundDevice = results.firstWhere( - (r) => r.advertisementData.serviceUuids.contains( - Guid("0000fee0-0000-1000-8000-00805f9b34fb"), - ), + (result) { + final matchesUuid = result.advertisementData.serviceUuids + .contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb")); + + final deviceName = result.device.name.trim().toLowerCase(); + final matchesName = mode == BadgeScanMode.any || + normalizedAllowedNames.contains(deviceName); + + return matchesUuid && matchesName; + }, orElse: () => throw Exception("Matching device not found."), ); isCompleted = true; - await FlutterBluePlus.stopScan(); + FlutterBluePlus.stopScan(); toast.showToast('Device found. Connecting...'); - nextStateCompleter.complete( - ConnectState(scanResult: foundDevice, manager: manager), - ); - } catch (_) { - // Ignore and keep scanning + + nextStateCompleter.complete(ConnectState( + scanResult: foundDevice, + manager: manager, + )); + } catch (e) { + logger.w("No matching device found in this batch: $e"); } }, onError: (e) { @@ -54,12 +75,11 @@ class ScanState extends NormalBleState { } }, ); - await FlutterBluePlus.startScan( withServices: [Guid("0000fee0-0000-1000-8000-00805f9b34fb")], - removeIfGone: const Duration(seconds: 5), + removeIfGone: Duration(seconds: 5), continuousUpdates: true, - timeout: const Duration(seconds: 15), + timeout: const Duration(seconds: 15), // Reduced scan timeout. ); await Future.delayed(const Duration(seconds: 1)); diff --git a/lib/main.dart b/lib/main.dart index 7ea48008c..30b13ddc7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/providers/BadgeScanProvider.dart'; import 'package:badgemagic/providers/getitlocator.dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/view/about_us_screen.dart'; @@ -23,6 +24,9 @@ void main() { providers: [ ChangeNotifierProvider( create: (context) => getIt()), + ChangeNotifierProvider( + create: (_) => getIt(), + ), ], child: const MyApp(), )); diff --git a/lib/providers/BadgeScanProvider.dart b/lib/providers/BadgeScanProvider.dart new file mode 100644 index 000000000..120cb4065 --- /dev/null +++ b/lib/providers/BadgeScanProvider.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +enum BadgeScanMode { any, specific } + +class BadgeScanProvider with ChangeNotifier { + BadgeScanMode _mode = BadgeScanMode.any; + List _badgeNames = ['LSLED', 'VBLAB']; + bool _isLoaded = false; + + BadgeScanMode get mode => _mode; + List get badgeNames => List.unmodifiable(_badgeNames); + bool get isLoaded => _isLoaded; + + BadgeScanProvider() { + _loadFromPrefs(); // Load persisted values in background + } + + // --- Persistence helpers --- + Future _loadFromPrefs() async { + final prefs = await SharedPreferences.getInstance(); + + // Load scan mode + final modeIndex = prefs.getInt('badge_scan_mode'); + if (modeIndex != null) { + _mode = BadgeScanMode.values[modeIndex]; + } + + // Load badge names + final storedNames = prefs.getStringList('badge_names'); + if (storedNames != null && storedNames.isNotEmpty) { + _badgeNames = storedNames; + } + + _isLoaded = true; + notifyListeners(); // Notify UI that values are loaded + } + + Future _saveToPrefs() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt('badge_scan_mode', _mode.index); + await prefs.setStringList('badge_names', _badgeNames); + } + + // --- Public methods to update values --- + void setMode(BadgeScanMode mode) { + _mode = mode; + _saveToPrefs(); + notifyListeners(); + } + + void setBadgeNames(List names) { + _badgeNames = names.where((name) => name.trim().isNotEmpty).toList(); + _saveToPrefs(); + notifyListeners(); + } + + void addBadgeName(String name) { + if (name.trim().isEmpty) return; + _badgeNames.add(name.trim()); + _saveToPrefs(); + notifyListeners(); + } + + void removeBadgeNameAt(int index) { + if (index < 0 || index >= _badgeNames.length) return; + _badgeNames.removeAt(index); + _saveToPrefs(); + notifyListeners(); + } + + void updateBadgeName(int index, String newName) { + if (index < 0 || index >= _badgeNames.length) return; + _badgeNames[index] = newName.trim(); + _saveToPrefs(); + notifyListeners(); + } +} diff --git a/lib/providers/animation_badge_provider.dart b/lib/providers/animation_badge_provider.dart index 00d79dd5b..080d491e0 100644 --- a/lib/providers/animation_badge_provider.dart +++ b/lib/providers/animation_badge_provider.dart @@ -17,22 +17,20 @@ import 'package:badgemagic/badge_animation/ani_pacman.dart'; import 'package:badgemagic/badge_animation/ani_chevron_left.dart'; import 'package:badgemagic/badge_animation/ani_diamond.dart'; import 'package:badgemagic/badge_animation/ani_broken_hearts.dart'; -import 'package:badgemagic/badge_animation/animation_abstract.dart'; -import 'package:badgemagic/badge_effect/badgeeffectabstract.dart'; -import 'package:badgemagic/badge_effect/flash_effect.dart'; -import 'package:badgemagic/badge_effect/invert_led_effect.dart'; -import 'package:badgemagic/badge_effect/marquee_effect.dart'; -import 'package:badgemagic/constants.dart'; -import 'package:flutter/material.dart'; - import 'package:badgemagic/badge_animation/ani_cupid.dart'; import 'package:badgemagic/badge_animation/ani_feet.dart'; import 'package:badgemagic/badge_animation/ani_fish.dart'; import 'package:badgemagic/badge_animation/ani_diagonal.dart'; - import 'package:badgemagic/badge_animation/ani_emergency.dart'; import 'package:badgemagic/badge_animation/ani_beating_hearts.dart'; import 'package:badgemagic/badge_animation/ani_fireworks.dart'; +import 'package:badgemagic/badge_animation/animation_abstract.dart'; +import 'package:badgemagic/badge_effect/badgeeffectabstract.dart'; +import 'package:badgemagic/badge_effect/flash_effect.dart'; +import 'package:badgemagic/badge_effect/invert_led_effect.dart'; +import 'package:badgemagic/badge_effect/marquee_effect.dart'; +import 'package:badgemagic/constants.dart'; +import 'package:flutter/material.dart'; Map animationMap = { 0: LeftAnimation(), @@ -262,6 +260,7 @@ class AnimationBadgeProvider extends ChangeNotifier { required bool flash, required bool marquee, required bool invert, + required BuildContext context, }) async { final int aniIndex = getAnimationIndex() ?? 0; final int selectedSpeed = speedDialProvider.getOuterValue(); @@ -301,6 +300,7 @@ class AnimationBadgeProvider extends ChangeNotifier { modeValueMap[aniIndex], null, false, + context, ); } } diff --git a/lib/providers/badge_message_provider.dart b/lib/providers/badge_message_provider.dart index e9afc6f49..03fc447b9 100644 --- a/lib/providers/badge_message_provider.dart +++ b/lib/providers/badge_message_provider.dart @@ -1,6 +1,5 @@ import 'dart:io'; import 'dart:math'; -import 'dart:ui'; import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart'; import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; @@ -12,7 +11,9 @@ 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/badge_animation/ani_fish.dart'; +import 'package:badgemagic/providers/BadgeScanProvider.dart'; import 'package:badgemagic/providers/imageprovider.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:get_it/get_it.dart'; import 'package:logger/logger.dart'; @@ -23,6 +24,7 @@ import 'package:badgemagic/badge_animation/ani_diagonal.dart'; import 'package:badgemagic/badge_animation/ani_emergency.dart'; import 'package:badgemagic/badge_animation/ani_beating_hearts.dart'; import 'package:badgemagic/badge_animation/ani_fireworks.dart'; +import 'package:provider/provider.dart'; Map modeValueMap = { 0: Mode.left, @@ -91,14 +93,28 @@ class BadgeMessageProvider { } } - Future transferData(DataTransferManager manager) async { + Future transferData( + DataTransferManager manager, + BuildContext? context, + ) async { + final scanProvider = context != null + ? Provider.of(context, listen: false) + : null; + + final BleState? initialState = ScanState( + manager: manager, + mode: scanProvider!.mode, + allowedNames: scanProvider.badgeNames, + ); + + BleState? state = initialState; DateTime now = DateTime.now(); - BleState? state = ScanState(manager: manager); + while (state != null) { state = await state.process(); } - logger.d("Time to transfer data is = ${DateTime.now().difference(now)}"); + logger.d("Time to transfer data: ${DateTime.now().difference(now)}"); logger.d(".......Data transfer completed......."); } @@ -111,6 +127,7 @@ class BadgeMessageProvider { Mode? mode, Map? jsonData, bool isSavedBadge, + BuildContext context, {TextStyle? textStyle}) async { if (await FlutterBluePlus.isSupported == false) { ToastUtils().showErrorToast('Bluetooth is not supported by the device'); @@ -210,7 +227,7 @@ class BadgeMessageProvider { } DataTransferManager manager = DataTransferManager(data); - await transferData(manager); + await transferData(manager, context); } } @@ -257,7 +274,7 @@ Future transferFireworksAnimation( Data data = Data(messages: frames); DataTransferManager manager = DataTransferManager(data); - await badgeDataProvider.transferData(manager); + await badgeDataProvider.transferData(manager, null); logger.i('💡 Fireworks animation transfer completed successfully!'); } @@ -305,7 +322,7 @@ Future transferBeatingHeartsAnimation( Data data = Data(messages: heartFrames); DataTransferManager manager = DataTransferManager(data); - await badgeDataProvider.transferData(manager); + await badgeDataProvider.transferData(manager, null); logger.i('💡 Beating Hearts animation transfer completed successfully!'); } @@ -365,7 +382,7 @@ Future transferEmergencyAnimation( Data data = Data(messages: rotatedFrames); DataTransferManager manager = DataTransferManager(data); - await badgeDataProvider.transferData(manager); + await badgeDataProvider.transferData(manager, null); logger.i('💡 Emergency animation transfer completed successfully!'); } @@ -423,7 +440,7 @@ Future transferDiagonalAnimation( logger.i('V Diagonal Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); logger.i('V Diagonal animation transfer completed successfully!'); } catch (e, st) { logger.e('⛔ V Diagonal animation transfer failed: $e\n$st'); @@ -486,7 +503,7 @@ Future transferFishAnimation( logger.i('🐟 Fish Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); logger.i('🐟 Fish animation transfer completed successfully!'); } catch (e, st) { logger.e('⛔ Fish animation transfer failed: $e\n$st'); @@ -667,7 +684,7 @@ Future transferPacmanAnimation( Data data = Data(messages: pacmanFrames); logger.i('💡 Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); } catch (e, st) { logger.e('⛔ Pacman animation transfer failed: $e\n$st'); } @@ -741,7 +758,7 @@ Future transferChevronAnimation( Data data = Data(messages: chevronFrames); logger.i('💡 Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); } catch (e, st) { logger.e('⛔ Chevron animation transfer failed: $e\n$st'); } @@ -802,7 +819,7 @@ Future transferDiamondAnimation( Data data = Data(messages: diamondFrames); logger.i('💡 Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); } catch (e, st) { logger.e('⛔ Diamond animation transfer failed: $e\n$st'); } @@ -925,7 +942,7 @@ Future transferBrokenHeartsAnimation( Data data = Data(messages: heartFrames); logger.i('💡 Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); } catch (e, st) { logger.e('⛔ Broken Hearts animation transfer failed: $e\n$st'); } @@ -1006,7 +1023,7 @@ Future transferFeetAnimation( Data data = Data(messages: feetFrames); logger.i('🦶 Feet Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); } catch (e, st) { logger.e('⛔ Feet animation transfer failed: $e\n$st'); } @@ -1050,7 +1067,7 @@ Future transferCupidAnimation( Data data = Data(messages: cupidFrames); logger.i('💘 Cupid Data object created. Starting transfer...'); try { - await badgeDataProvider.transferData(DataTransferManager(data)); + await badgeDataProvider.transferData(DataTransferManager(data), null); } catch (e, st) { logger.e('⛔ Cupid animation transfer failed: $e\n$st'); } diff --git a/lib/providers/getitlocator.dart b/lib/providers/getitlocator.dart index 775c3a7f3..dc48c6bc6 100644 --- a/lib/providers/getitlocator.dart +++ b/lib/providers/getitlocator.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/providers/BadgeScanProvider.dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:get_it/get_it.dart'; @@ -5,4 +6,5 @@ final GetIt getIt = GetIt.instance; void setupLocator() { getIt.registerLazySingleton(() => InlineImageProvider()); + getIt.registerLazySingleton(() => BadgeScanProvider()); } diff --git a/lib/view/badgeScanSettingsWidget.dart b/lib/view/badgeScanSettingsWidget.dart new file mode 100644 index 000000000..58d00a69f --- /dev/null +++ b/lib/view/badgeScanSettingsWidget.dart @@ -0,0 +1,146 @@ +import 'package:badgemagic/providers/BadgeScanProvider.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class BadgeScanSettingsWidget extends StatefulWidget { + final Function(BadgeScanMode mode, List names)? onSave; + + const BadgeScanSettingsWidget({super.key, this.onSave}); + + @override + State createState() => + _BadgeScanSettingsWidgetState(); +} + +class _BadgeScanSettingsWidgetState extends State { + late BadgeScanMode _mode; + final List _controllers = []; + + @override + void initState() { + super.initState(); + final provider = Provider.of(context, listen: false); + _mode = provider.mode; + } + + void _addBadgeName() { + setState(() { + _controllers.add(TextEditingController()); + }); + } + + void _removeBadgeName(int index) { + setState(() { + _controllers.removeAt(index).dispose(); + }); + } + + Future _onSave() async { + final updatedNames = _controllers + .map((c) => c.text.trim()) + .where((name) => name.isNotEmpty) + .toList(); + + final provider = Provider.of(context, listen: false); + provider.setMode(_mode); + provider.setBadgeNames(updatedNames); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Scan settings saved successfully')), + ); + + widget.onSave?.call(_mode, updatedNames); + if (mounted) Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, child) { + if (!provider.isLoaded) { + return const Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } + + if (_controllers.isEmpty) { + // Initialize controllers only after provider is loaded + for (var name in provider.badgeNames) { + _controllers.add(TextEditingController(text: name)); + } + } + + return Scaffold( + appBar: AppBar( + title: const Text('Badge Scan Settings'), + actions: [ + IconButton( + icon: const Icon(Icons.save), + onPressed: _onSave, + ), + ], + ), + body: Column( + children: [ + RadioListTile( + title: const Text('Connect to any badge'), + value: BadgeScanMode.any, + groupValue: _mode, + onChanged: (val) => setState(() => _mode = val!), + ), + RadioListTile( + title: const Text('Connect to badges with the following names'), + value: BadgeScanMode.specific, + groupValue: _mode, + onChanged: (val) => setState(() => _mode = val!), + ), + if (_mode == BadgeScanMode.specific) + Expanded( + child: ListView.builder( + itemCount: _controllers.length, + itemBuilder: (context, index) { + return Row( + children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12.0), + child: TextField( + controller: _controllers[index], + decoration: const InputDecoration( + labelText: 'Badge Name', + ), + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle_outline), + onPressed: () => _removeBadgeName(index), + ), + ], + ); + }, + ), + ), + if (_mode == BadgeScanMode.specific) + Padding( + padding: const EdgeInsets.all(12.0), + child: ElevatedButton.icon( + onPressed: _addBadgeName, + icon: const Icon(Icons.add), + label: const Text("Add more"), + ), + ), + ], + ), + ); + }, + ); + } + + @override + void dispose() { + for (var c in _controllers) c.dispose(); + super.dispose(); + } +} diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index fffe5b545..4d713a438 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -33,7 +33,6 @@ import 'package:provider/provider.dart'; class HomeScreen extends StatefulWidget { // Add parameters for saved badge data when editing - final String? savedBadgeFilename; final int? initialSpeed; @@ -53,29 +52,34 @@ class _HomeScreenState extends State AutomaticKeepAliveClientMixin, WidgetsBindingObserver { late final TabController _tabController; - AnimationBadgeProvider animationProvider = AnimationBadgeProvider(); + final AnimationBadgeProvider animationProvider = AnimationBadgeProvider(); late SpeedDialProvider speedDialProvider; - BadgeMessageProvider badgeData = BadgeMessageProvider(); - ImageUtils imageUtils = ImageUtils(); - InlineImageProvider inlineImageProvider = + final BadgeMessageProvider badgeData = BadgeMessageProvider(); + final ImageUtils imageUtils = ImageUtils(); + final InlineImageProvider inlineImageProvider = GetIt.instance(); - bool isPrefixIconClicked = false; - int textfieldLength = 0; - String previousText = ''; final TextEditingController inlineimagecontroller = GetIt.instance.get().getController(); + + bool isPrefixIconClicked = false; bool isDialInteracting = false; + String previousText = ''; + String _cachedText = ''; String errorVal = ""; @override void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); inlineimagecontroller.addListener(handleTextChange); _setPortraitOrientation(); speedDialProvider = SpeedDialProvider(animationProvider); + // If initialSpeed is provided, set it immediately if (widget.initialSpeed != null) { speedDialProvider.setDialValue(widget.initialSpeed!); } + WidgetsBinding.instance.addPostFrameCallback((_) async { inlineImageProvider.setContext(context); @@ -85,8 +89,6 @@ class _HomeScreenState extends State } }); _startImageCaching(); - super.initState(); - _tabController = TabController(length: 4, vsync: this); } @@ -95,12 +97,15 @@ class _HomeScreenState extends State try { final (badgeText, badgeData, savedData) = await BadgeLoaderHelper.loadBadgeDataAndText(badgeFilename); + // Set the text in the controller inlineimagecontroller.text = badgeText; + // Set animation effects animationProvider.removeEffect(effectMap[0]); // Invert animationProvider.removeEffect(effectMap[1]); // Flash animationProvider.removeEffect(effectMap[2]); // Marquee + final message = badgeData.messages[0]; if (message.flash) { animationProvider.addEffect(effectMap[1]); @@ -113,9 +118,11 @@ class _HomeScreenState extends State savedData['invert'] == true) { animationProvider.addEffect(effectMap[0]); } + // Set animation mode int modeValue = BadgeLoaderHelper.parseAnimationMode(message.mode); animationProvider.setAnimationMode(animationMap[modeValue]); + // Set speed try { int speedDialValue = Speed.getIntValue(message.speed); @@ -123,6 +130,7 @@ class _HomeScreenState extends State } catch (e) { speedDialProvider.setDialValue(1); } + ToastUtils().showToast( "Editing badge: ${badgeFilename.substring(0, badgeFilename.length - 5)}"); } catch (e) { @@ -149,313 +157,34 @@ class _HomeScreenState extends State @override void dispose() { + WidgetsBinding.instance.removeObserver(this); inlineimagecontroller.removeListener(handleTextChange); + inlineimagecontroller.removeListener(_controllerListner); animationProvider.stopAnimation(); - WidgetsBinding.instance.removeObserver(this); _tabController.dispose(); super.dispose(); } @override - Widget build(BuildContext context) { - super.build(context); - InlineImageProvider inlineImageProvider = - Provider.of(context); - - return MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (context) => animationProvider, - ), - ChangeNotifierProvider( - create: (context) { - inlineImageProvider.getController().addListener(_controllerListner); - return speedDialProvider; - }, - ), - ], - child: DefaultTabController( - length: 4, - child: CommonScaffold( - index: 0, - title: 'Badge Magic', - body: SafeArea( - child: SingleChildScrollView( - physics: isDialInteracting - ? const NeverScrollableScrollPhysics() - : const AlwaysScrollableScrollPhysics(), - child: Column( - children: [ - AnimationBadge(), - Container( - margin: EdgeInsets.all(15.w), - child: Material( - color: drawerHeaderTitle, - borderRadius: BorderRadius.circular(10.r), - elevation: 4, - child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - focusedBorder: OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(10.r)), - borderSide: BorderSide(color: colorPrimary), - ), - ), - ), - ), - ), - Visibility( - visible: isPrefixIconClicked, - child: Container( - height: 170.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.r), - color: Colors.grey[200]), - margin: EdgeInsets.symmetric(horizontal: 15.w), - padding: EdgeInsets.symmetric( - vertical: 10.h, horizontal: 10.w), - child: VectorGridView())), - TabBar( - isScrollable: false, - indicatorSize: TabBarIndicatorSize.tab, - labelStyle: TextStyle(fontSize: 12), - unselectedLabelStyle: TextStyle(fontSize: 12), - labelColor: Colors.black, - unselectedLabelColor: mdGrey400, - indicatorColor: colorPrimary, - controller: _tabController, - splashFactory: InkRipple.splashFactory, - overlayColor: WidgetStateProperty.resolveWith( - (states) => states.contains(WidgetState.pressed) - ? dividerColor - : null, - ), - tabs: const [ - Tab(text: 'Speed'), - Tab(text: 'Animation'), - Tab(text: 'Transition'), - Tab(text: 'Effects'), - ], - ), - SizedBox( - height: 250.h, - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - controller: _tabController, - children: [ - GestureDetector( - onPanDown: (_) => - setState(() => isDialInteracting = true), - onPanCancel: () => - setState(() => isDialInteracting = false), - onPanEnd: (_) => - setState(() => isDialInteracting = false), - child: RadialDial(), - ), - TransitionTab(), - AnimationTab(), - EffectTab(), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.symmetric(vertical: 20.h), - child: Consumer( - builder: (context, animationProvider, _) { - final isSpecial = animationProvider - .isSpecialAnimationSelected(); - if (isSpecial) { - // Only show Transfer button, centered - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: - EdgeInsets.symmetric(vertical: 20.h), - child: GestureDetector( - onTap: () async { - await animationProvider - .handleAnimationTransfer( - badgeData: badgeData, - inlineImageProvider: - inlineImageProvider, - speedDialProvider: - speedDialProvider, - flash: animationProvider - .isEffectActive(FlashEffect()), - marquee: animationProvider - .isEffectActive( - MarqueeEffect()), - invert: animationProvider - .isEffectActive( - InvertLEDEffect()), - ); - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(2.r), - color: mdGrey400, - ), - child: const Text('Transfer'), - ), - ), - ), - ], - ); - } else { - // Show both Save and Transfer as before - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: - EdgeInsets.symmetric(vertical: 20.h), - child: GestureDetector( - onTap: () async { - if (inlineimagecontroller.text - .trim() - .isEmpty) { - ToastUtils().showToast( - "Please enter a message"); - return; - } - // If we're editing an existing badge, update it instead of showing save dialog - if (widget.savedBadgeFilename != - null) { - SavedBadgeProvider - savedBadgeProvider = - SavedBadgeProvider(); - String baseFilename = - widget.savedBadgeFilename!; - if (baseFilename - .endsWith('.json')) { - baseFilename = - baseFilename.substring(0, - baseFilename.length - 5); - } - await savedBadgeProvider - .updateBadgeData( - baseFilename, // Pass the filename without .json extension - inlineimagecontroller.text, - animationProvider.isEffectActive( - FlashEffect()), - animationProvider.isEffectActive( - MarqueeEffect()), - animationProvider.isEffectActive( - InvertLEDEffect()), - speedDialProvider.getOuterValue(), - animationProvider - .getAnimationIndex() ?? - 1, - ); - ToastUtils().showToast( - "Badge Updated Successfully"); - Navigator.pushNamedAndRemoveUntil( - context, - '/savedBadge', - (route) => false); - } else { - // Show save dialog for new badges - showDialog( - context: context, - builder: (context) { - return SaveBadgeDialog( - speed: speedDialProvider, - animationProvider: - animationProvider, - textController: - inlineimagecontroller, - isInverse: animationProvider - .isEffectActive( - InvertLEDEffect()), - ); - }, - ); - } - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(2.r), - color: mdGrey400, - ), - child: const Text('Save'), - ), - ), - ), - SizedBox(width: 100.w), - Container( - padding: - EdgeInsets.symmetric(vertical: 20.h), - child: GestureDetector( - onTap: () async { - await animationProvider - .handleAnimationTransfer( - badgeData: badgeData, - inlineImageProvider: - inlineImageProvider, - speedDialProvider: - speedDialProvider, - flash: animationProvider - .isEffectActive(FlashEffect()), - marquee: animationProvider - .isEffectActive( - MarqueeEffect()), - invert: animationProvider - .isEffectActive( - InvertLEDEffect()), - ); - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 20.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(2.r), - color: mdGrey400, - ), - child: const Text('Transfer'), - ), - ), - ), - ], - ); - } - }, - ), - ), - ], - ) - ], - ), - ), - ), - scaffoldKey: const Key(homeScreenTitleKey), - )), - ); + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.resumed) { + if (inlineimagecontroller.text.trim().isEmpty && + _cachedText.trim().isNotEmpty) { + inlineimagecontroller.text = _cachedText; + } + animationProvider.badgeAnimation( + inlineimagecontroller.text, + Converters(), + animationProvider.isEffectActive(InvertLEDEffect()), + ); + if (mounted) setState(() {}); + } else if (state == AppLifecycleState.paused) { + _cachedText = inlineimagecontroller.text; + animationProvider.stopAnimation(); + } else if (state == AppLifecycleState.inactive) { + animationProvider.stopAnimation(); + } } void handleTextChange() { @@ -496,25 +225,322 @@ class _HomeScreenState extends State } void _controllerListner() { - animationProvider.badgeAnimation(inlineImageProvider.getController().text, - Converters(), animationProvider.isEffectActive(InvertLEDEffect())); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider.isEffectActive(InvertLEDEffect()), + ); } @override - bool get wantKeepAlive => true; + Widget build(BuildContext context) { + super.build(context); + InlineImageProvider inlineImageProvider = + Provider.of(context); - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - inlineimagecontroller.clear(); - previousText = ''; - animationProvider.stopAllAnimations.call(); // If method exists - animationProvider.initializeAnimation.call(); // If method exists - if (mounted) setState(() {}); - } else if (state == AppLifecycleState.paused || - state == AppLifecycleState.inactive) { - animationProvider.stopAnimation(); - } + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => animationProvider, + ), + ChangeNotifierProvider( + create: (context) { + inlineImageProvider.getController().addListener(_controllerListner); + return speedDialProvider; + }, + ), + ], + child: DefaultTabController( + length: 4, + child: CommonScaffold( + index: 0, + title: 'Badge Magic', + scaffoldKey: const Key(homeScreenTitleKey), + body: SafeArea( + child: SingleChildScrollView( + physics: isDialInteracting + ? const NeverScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics(), + child: Column( + children: [ + AnimationBadge(), + Container( + margin: EdgeInsets.all(15.w), + child: Material( + color: drawerHeaderTitle, + borderRadius: BorderRadius.circular(10.r), + elevation: 4, + child: ExtendedTextField( + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + focusedBorder: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(10.r)), + borderSide: BorderSide(color: colorPrimary), + ), + ), + ), + ), + ), + Visibility( + visible: isPrefixIconClicked, + child: Container( + height: 170.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Colors.grey[200], + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric( + vertical: 10.h, horizontal: 10.w), + child: VectorGridView(), + ), + ), + TabBar( + isScrollable: false, + indicatorSize: TabBarIndicatorSize.tab, + labelStyle: TextStyle(fontSize: 12), + unselectedLabelStyle: TextStyle(fontSize: 12), + labelColor: Colors.black, + unselectedLabelColor: mdGrey400, + indicatorColor: colorPrimary, + controller: _tabController, + splashFactory: InkRipple.splashFactory, + overlayColor: WidgetStateProperty.resolveWith( + (states) => states.contains(WidgetState.pressed) + ? dividerColor + : null, + ), + tabs: const [ + Tab(text: 'Speed'), + Tab(text: 'Animation'), + Tab(text: 'Transition'), + Tab(text: 'Effects'), + ], + ), + SizedBox( + height: 250.h, + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, + children: [ + GestureDetector( + onPanDown: (_) => + setState(() => isDialInteracting = true), + onPanCancel: () => + setState(() => isDialInteracting = false), + onPanEnd: (_) => + setState(() => isDialInteracting = false), + child: RadialDial(), + ), + TransitionTab(), + AnimationTab(), + EffectTab(), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 20.h), + child: Consumer( + builder: (context, animationProvider, _) { + final isSpecial = + animationProvider.isSpecialAnimationSelected(); + if (isSpecial) { + // Only show Transfer button, centered + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: + EdgeInsets.symmetric(vertical: 20.h), + child: GestureDetector( + onTap: () async { + await animationProvider + .handleAnimationTransfer( + badgeData: badgeData, + inlineImageProvider: + inlineImageProvider, + speedDialProvider: speedDialProvider, + flash: animationProvider + .isEffectActive(FlashEffect()), + marquee: animationProvider + .isEffectActive(MarqueeEffect()), + invert: + animationProvider.isEffectActive( + InvertLEDEffect()), + context: context, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Transfer'), + ), + ), + ), + ], + ); + } else { + // Show both Save and Transfer as before + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: + EdgeInsets.symmetric(vertical: 20.h), + child: GestureDetector( + onTap: () async { + if (inlineimagecontroller.text + .trim() + .isEmpty) { + ToastUtils().showToast( + "Please enter a message"); + return; + } + // If we're editing an existing badge, update it instead of showing save dialog + if (widget.savedBadgeFilename != null) { + SavedBadgeProvider + savedBadgeProvider = + SavedBadgeProvider(); + String baseFilename = + widget.savedBadgeFilename!; + if (baseFilename.endsWith('.json')) { + baseFilename = + baseFilename.substring( + 0, baseFilename.length - 5); + } + await savedBadgeProvider + .updateBadgeData( + baseFilename, // Pass the filename without .json extension + inlineimagecontroller.text, + animationProvider + .isEffectActive(FlashEffect()), + animationProvider.isEffectActive( + MarqueeEffect()), + animationProvider.isEffectActive( + InvertLEDEffect()), + speedDialProvider.getOuterValue(), + animationProvider + .getAnimationIndex() ?? + 1, + ); + ToastUtils().showToast( + "Badge Updated Successfully"); + Navigator.pushNamedAndRemoveUntil( + context, + '/savedBadge', + (route) => false); + } else { + // Show save dialog for new badges + showDialog( + context: context, + builder: (context) { + return SaveBadgeDialog( + speed: speedDialProvider, + animationProvider: + animationProvider, + textController: + inlineimagecontroller, + isInverse: animationProvider + .isEffectActive( + InvertLEDEffect()), + ); + }, + ); + } + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Save'), + ), + ), + ), + SizedBox(width: 100.w), + Container( + padding: + EdgeInsets.symmetric(vertical: 20.h), + child: GestureDetector( + onTap: () async { + if (inlineimagecontroller.text + .trim() + .isEmpty) { + ToastUtils().showErrorToast( + "Please enter a message"); + return; + } + await animationProvider + .handleAnimationTransfer( + badgeData: badgeData, + inlineImageProvider: + inlineImageProvider, + speedDialProvider: speedDialProvider, + flash: animationProvider + .isEffectActive(FlashEffect()), + marquee: animationProvider + .isEffectActive(MarqueeEffect()), + invert: + animationProvider.isEffectActive( + InvertLEDEffect()), + context: context, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Transfer'), + ), + ), + ), + ], + ); + } + }, + ), + ), + ], + ) + ], + ), + ), + ), + ), + ), + ); } + + @override + bool get wantKeepAlive => true; } diff --git a/lib/view/save_badge_screen.dart b/lib/view/save_badge_screen.dart index 5937b3036..22fece260 100644 --- a/lib/view/save_badge_screen.dart +++ b/lib/view/save_badge_screen.dart @@ -227,7 +227,6 @@ class _SaveBadgeScreenState extends State { badgeData['messages'][0]); badgeDataList.add(message); } - while (badgeDataList.length < 8) { badgeDataList.add(Message(text: [])); } @@ -253,15 +252,15 @@ class _SaveBadgeScreenState extends State { final data = Data(messages: badgeDataList); badgeMessageProvider.checkAndTransfer( - null, - null, - null, - null, - null, - null, - data.toJson(), - true, - ); + null, + null, + null, + null, + null, + null, + data.toJson(), + true, + context); } : null, style: ElevatedButton.styleFrom( diff --git a/lib/view/settings_screen.dart b/lib/view/settings_screen.dart index e986d4c27..2a5461762 100644 --- a/lib/view/settings_screen.dart +++ b/lib/view/settings_screen.dart @@ -1,7 +1,8 @@ -import 'package:badgemagic/constants.dart'; +import 'package:badgemagic/providers/BadgeScanProvider.dart'; import 'package:badgemagic/view/widgets/common_scaffold_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -12,15 +13,16 @@ class SettingsScreen extends StatefulWidget { class SettingsScreenState extends State { String selectedLanguage = 'ENGLISH'; - String selectedBadge = 'LSLED'; - final List languages = ['ENGLISH', 'CHINESE']; - final List badges = ['LSLED', 'VBLAB']; + + late BadgeScanMode _scanMode; + late List _controllers; + bool _initialized = false; @override void initState() { - _setOrientation(); super.initState(); + _setOrientation(); } void _setOrientation() { @@ -30,83 +32,157 @@ class SettingsScreenState extends State { ]); } + @override + void dispose() { + if (_initialized) { + for (final controller in _controllers) { + controller.dispose(); + } + } + super.dispose(); + } + @override Widget build(BuildContext context) { - return CommonScaffold( - index: 4, - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Language', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.symmetric(horizontal: 12), - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: selectedLanguage, - isExpanded: true, - icon: Icon(Icons.arrow_drop_down, color: mdGrey400), - onChanged: (String? newValue) { - setState(() { - selectedLanguage = newValue!; - }); - }, - items: - languages.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value, - style: const TextStyle(color: Colors.black)), - ); - }).toList(), + return Consumer( + builder: (context, provider, child) { + if (!provider.isLoaded) { + return const Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } + + // Initialize controllers once after provider is loaded + if (!_initialized) { + _scanMode = provider.mode; + _controllers = provider.badgeNames + .map((name) => TextEditingController(text: name)) + .toList(); + _initialized = true; + } + + return CommonScaffold( + index: 4, + title: 'Badge Magic', + body: Padding( + padding: const EdgeInsets.all(16.0), + child: ListView( + children: [ + const Text('Language', + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + _buildDropdown( + selectedValue: selectedLanguage, + values: languages, + onChanged: (value) => + setState(() => selectedLanguage = value), ), - ), - ), - const SizedBox(height: 20), - const Text( - 'Select Badge', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.symmetric(horizontal: 12), - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: selectedBadge, - isExpanded: true, - icon: Icon(Icons.arrow_drop_down, color: mdGrey400), - onChanged: (String? newValue) { - setState(() { - selectedBadge = newValue!; - }); - }, - items: badges.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value, - style: const TextStyle(color: Colors.black)), + const SizedBox(height: 24), + const Text('Badge Scan Mode', + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + RadioListTile( + title: const Text('Connect to any badge'), + value: BadgeScanMode.any, + groupValue: _scanMode, + onChanged: (value) => setState(() => _scanMode = value!), + ), + RadioListTile( + title: + const Text('Connect to badges with the following names'), + value: BadgeScanMode.specific, + groupValue: _scanMode, + onChanged: (value) => setState(() => _scanMode = value!), + ), + if (_scanMode == BadgeScanMode.specific) + ..._controllers.asMap().entries.map((entry) { + final index = entry.key; + final controller = entry.value; + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: TextField( + controller: controller, + decoration: const InputDecoration( + hintText: 'Badge name', + border: OutlineInputBorder(), + ), + ), + ), + ), + IconButton( + icon: const Icon(Icons.remove_circle_outline), + onPressed: () { + setState(() { + controller.dispose(); + _controllers.removeAt(index); + }); + }, + ), + ], ); }).toList(), - ), - ), + if (_scanMode == BadgeScanMode.specific) + TextButton.icon( + onPressed: () => setState(() { + _controllers.add(TextEditingController()); + }), + icon: const Icon(Icons.add), + label: const Text('Add More'), + ), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: () { + provider.setMode(_scanMode); + provider.setBadgeNames( + _controllers.map((c) => c.text.trim()).toList(), + ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Scan settings saved')), + ); + }, + icon: const Icon(Icons.save), + label: const Text("Save Settings"), + ) + ], ), - ], + ), + ); + }, + ); + } + + Widget _buildDropdown({ + required String selectedValue, + required List values, + required Function(String) onChanged, + }) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(horizontal: 12), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: selectedValue, + isExpanded: true, + icon: const Icon(Icons.arrow_drop_down, color: Colors.grey), + onChanged: (String? newValue) { + if (newValue != null) onChanged(newValue); + }, + items: values.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value, style: const TextStyle(color: Colors.black)), + ); + }).toList(), ), ), - title: 'Badge Magic', ); } } diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index 2bc5ebed4..00e36c2b7 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -8,7 +8,7 @@ import 'package:badgemagic/providers/animation_badge_provider.dart'; import 'package:badgemagic/providers/badge_message_provider.dart'; import 'package:badgemagic/providers/badge_slot_provider..dart'; import 'package:badgemagic/providers/saved_badge_provider.dart'; -import 'package:badgemagic/view/homescreen.dart'; +import 'package:badgemagic/view/draw_badge_screen.dart'; import 'package:badgemagic/view/widgets/badge_delete_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -35,274 +35,167 @@ class SaveBadgeCard extends StatelessWidget { this.onTap, }); - // Helper methods to safely access badge data properties - bool _safeGetFlashValue(Map data) { - try { - return file.jsonToData(data).messages[0].flash; - } catch (e) { - // If there's an error, default to false - return false; - } - } - - bool _safeGetMarqueeValue(Map data) { - try { - return file.jsonToData(data).messages[0].marquee; - } catch (e) { - // If there's an error, default to false - return false; - } - } - - bool _safeGetInvertValue(Map data) { - try { - if (data.containsKey('messages') && - data['messages'] is List && - data['messages'].isNotEmpty && - data['messages'][0] is Map) { - return data['messages'][0]['invert'] ?? false; - } - return false; - } catch (e) { - // If there's an error, default to false - return false; - } - } - @override Widget build(BuildContext context) { BadgeMessageProvider badge = BadgeMessageProvider(); - return GestureDetector( - onLongPress: onLongPress, - onTap: onTap, - child: Container( - width: 370.w, - padding: EdgeInsets.all(6.dg), - margin: EdgeInsets.all(10.dg), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(6.dg), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), - ), - ], - border: - isSelected ? Border.all(color: colorPrimary, width: 2) : null, + return Container( + width: 370.w, + padding: EdgeInsets.all(6.dg), + margin: EdgeInsets.all(10.dg), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6.dg), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Wrapping the text with Flexible to ensure it doesn't overflow. - Flexible( - child: Padding( - padding: EdgeInsets.only( - right: 8 - .w), // Adding some padding to separate text and buttons - child: Text( - badgeData.key.substring(0, badgeData.key.length - 5), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - softWrap: true, - overflow: TextOverflow - .ellipsis, // Use ellipsis to indicate overflowed text - maxLines: 1, // Limit to 1 line for a cleaner look - ), + // Wrapping the text with Flexible to ensure it doesn't overflow. + Flexible( + child: Padding( + padding: EdgeInsets.only( + right: 8 + .w), // Adding some padding to separate text and buttons. + child: Text( + badgeData.key.substring(0, badgeData.key.length - 5), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, ), + softWrap: true, + overflow: TextOverflow + .ellipsis, // Use ellipsis to indicate overflowed text + maxLines: 1, // Limit to 1 line for a cleaner look ), - Consumer( - builder: (context, provider, widget) => Row( - mainAxisSize: MainAxisSize.min, // Keep the row compact - children: [ - IconButton( - icon: Image.asset( - "assets/icons/t_play.png", - height: 20, - color: Colors.black, - ), - onPressed: () { - provider.savedBadgeAnimation( - badgeData.value, - Provider.of(context, - listen: false)); - }, - ), - IconButton( - icon: const Icon( - Icons.edit, - color: Colors.black, - ), - onPressed: () async { - final shouldEdit = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Edit Badge'), - content: const Text( - 'Do you want to edit this badge?'), - actions: [ - TextButton( - onPressed: () => - Navigator.pop(context, false), - child: const Text('No'), - ), - TextButton( - onPressed: () => - Navigator.pop(context, true), - child: const Text('Yes'), - ), - ], - ), - ); - if (shouldEdit == true) { - // Extract the speed value from the saved badge - final speed = Speed.getIntValue(file - .jsonToData(badgeData.value) - .messages[0] - .speed); - String badgeFilename = badgeData.key; - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => HomeScreen( - savedBadgeFilename: badgeFilename, - initialSpeed: speed, // Pass the speed value - ), - ), - (route) => false, // Remove all previous routes - ); - } - }, - ), - IconButton( - icon: Image.asset( - "assets/icons/t_updown.png", - height: 24.h, - color: Colors.black, - ), - onPressed: () { - logger.d("BadgeData: ${badgeData.value}"); - //We can Acrtually call a method to generate the data just by transffering the JSON data - //so we would not necessarily need the Providers. - badge.checkAndTransfer(null, null, null, null, null, - null, badgeData.value, true); - }, - ), - IconButton( - icon: const Icon( - Icons.share, - color: Colors.black, - ), - onPressed: () { - file.shareBadgeData(badgeData.key); - }, - ), - IconButton( - icon: const Icon( - Icons.delete, - color: Colors.black, + ), + ), + Consumer( + builder: (context, provider, widget) => Row( + mainAxisSize: MainAxisSize.min, // Keep the row compact + children: [ + IconButton( + icon: Image.asset( + "assets/icons/t_play.png", + height: 20, + color: Colors.black, + ), + onPressed: () { + provider.savedBadgeAnimation( + badgeData.value, + Provider.of(context, + listen: false)); + }, + ), + IconButton( + icon: const Icon( + Icons.edit, + color: Colors.black, + ), + onPressed: () { + List> data = hexStringToBool(file + .jsonToData(badgeData.value) + .messages[0] + .text + .join()) + .map((e) => e.map((e) => e ? 1 : 0).toList()) + .toList(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DrawBadge( + filename: badgeData.key, + isSavedCard: true, + badgeGrid: data, + ), ), - onPressed: () async { - //add a dialog for confirmation before deleting - await _showDeleteDialog(context) - .then((value) async { - if (value == true) { - file.deleteFile(badgeData.key); - toastUtils - .showToast("Badge Deleted Successfully"); - await refreshBadgesCallback(badgeData); - } - }); - }, - ), - ], + ); + }, ), - ), - ], + IconButton( + icon: Image.asset( + "assets/icons/t_updown.png", + height: 24.h, + color: Colors.black, + ), + onPressed: () { + logger.d("BadgeData: ${badgeData.value}"); + //We can Acrtually call a method to generate the data just by transffering the JSON data + //so we would not necessarily need the Providers. + badge.checkAndTransfer(null, null, null, null, null, + null, badgeData.value, true, context); + }, + ), + IconButton( + icon: const Icon( + Icons.share, + color: Colors.black, + ), + onPressed: () { + file.shareBadgeData(badgeData.key); + }, + ), + IconButton( + icon: const Icon( + Icons.delete, + color: Colors.black, + ), + onPressed: () async { + //add a dialog for confirmation before deleting + await _showDeleteDialog(context).then((value) async { + if (value == true) { + file.deleteFile(badgeData.key); + toastUtils.showToast("Badge Deleted Successfully"); + await refreshBadgesCallback(badgeData); + } + }); + }, + ), + ], + ), ), - SizedBox(height: 8.h), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ Row( children: [ - Row( - children: [ - Visibility( - visible: _safeGetFlashValue(badgeData.value), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 10.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Row( - children: [ - Image.asset( - "assets/icons/flash.png", - color: Colors.white, - height: 14.h, - ) - ], - ), - ), - ), - SizedBox( - width: 8.w, - ), - Visibility( - visible: _safeGetMarqueeValue(badgeData.value), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 12.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Row( - children: [ - Image.asset( - "assets/icons/square.png", - color: Colors.white, - height: 14.h, - ) - ], - ), - ), + Visibility( + visible: file.jsonToData(badgeData.value).messages[0].flash, + child: Container( + padding: + EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), ), - SizedBox( - width: 8.w, + child: Row( + children: [ + Image.asset( + "assets/icons/flash.png", + color: Colors.white, + height: 14.h, + ) + ], ), - Visibility( - visible: _safeGetInvertValue(badgeData.value), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 12.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Row( - children: [ - Image.asset( - "assets/icons/t_invert.png", - color: Colors.white, - height: 14.h, - ) - ], - ), - ), - ) - ], + ), ), - SizedBox(width: 8.w), - GestureDetector( - onTap: () {}, + SizedBox( + width: 8.w, + ), + Visibility( + visible: + file.jsonToData(badgeData.value).messages[0].marquee, child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), @@ -313,26 +206,19 @@ class SaveBadgeCard extends StatelessWidget { child: Row( children: [ Image.asset( - "assets/icons/t_double.png", + "assets/icons/square.png", color: Colors.white, height: 14.h, - ), - const SizedBox(width: 4), - Text( - Speed.getIntValue(file - .jsonToData(badgeData.value) - .messages[0] - .speed) - .toString(), - style: const TextStyle(color: Colors.white), - ), + ) ], ), ), ), - SizedBox(width: 8.w), - GestureDetector( - onTap: () {}, + SizedBox( + width: 8.w, + ), + Visibility( + visible: badgeData.value['messages'][0]['invert'] ?? false, child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), @@ -340,40 +226,94 @@ class SaveBadgeCard extends StatelessWidget { color: colorPrimary, borderRadius: BorderRadius.circular(100), ), - child: Text( - file - .jsonToData(badgeData.value) - .messages[0] - .mode - .toString() - .split('.') - .last - .toUpperCase(), - style: const TextStyle(color: Colors.white), + child: Row( + children: [ + Image.asset( + "assets/icons/t_invert.png", + color: Colors.white, + height: 14.h, + ) + ], ), ), + ) + ], + ), + SizedBox(width: 8.w), + GestureDetector( + onTap: () {}, + child: Container( + padding: + EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), ), - const Spacer(), - Consumer( - builder: (context, selectionProvider, _) { - final isSelected = - selectionProvider.isSelected(badgeData.key); - return Switch( - value: isSelected, - onChanged: (selectionProvider.canSelectMore || - isSelected) - ? (value) => - selectionProvider.toggleSelection(badgeData.key) - : null, - activeColor: colorPrimary, - ); - }, + child: Row( + children: [ + Image.asset( + "assets/icons/t_double.png", + color: Colors.white, + height: 14.h, + ), + const SizedBox(width: 4), + Text( + ( + Speed.getIntValue(file + .jsonToData(badgeData.value) + .messages[0] + .speed) + .toString(), + style: const TextStyle(color: Colors.white), + ) as String, + ) + ], ), - ], + ), + ), + SizedBox(width: 8.w), + GestureDetector( + onTap: () {}, + child: Container( + padding: + EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Text( + file + .jsonToData(badgeData.value) + .messages[0] + .mode + .toString() + .split('.') + .last + .toUpperCase(), + style: const TextStyle(color: Colors.white), + ), + ), + ), + const Spacer(), + Consumer( + builder: (context, selectionProvider, _) { + final isSelected = + selectionProvider.isSelected(badgeData.key); + return Switch( + value: isSelected, + onChanged: (selectionProvider.canSelectMore || isSelected) + ? (value) => + selectionProvider.toggleSelection(badgeData.key) + : null, + activeColor: colorPrimary, + ); + }, ), ], ), - )); + ], + ), + ); } Future _showDeleteDialog(BuildContext context) async { diff --git a/pubspec.lock b/pubspec.lock index b7472c88a..6c4a8a138 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -838,4 +838,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.8.0 <4.0.0" - flutter: ">=3.32.4" + flutter: ">=3.32.4" \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index d6659049c..f08ae2dba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ environment: dependencies: flutter: sdk: flutter + shared_preferences: 2.5.3 # The following adds the Cupertino Icons font to your application. @@ -110,4 +111,3 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -