From 6668786b67600d7bada9479e5560137f24482507 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Mon, 11 Aug 2025 00:17:21 +0530 Subject: [PATCH 01/18] feat(packages): add morse_tap package with gesture-based input - Implement MorseTapDetector widget with gesture recognition - Add MorseTextInput for real-time text conversion - Create MorseCodec algorithm with comprehensive character support - Include string extensions for easy Morse code conversion - Add complete example app with 3 demo screens - Implement gesture system: single tap = dot, double tap = dash, long press = space - Include 8 comprehensive test cases - Add full documentation and README --- packages/morse_tap/.gitignore | 31 + packages/morse_tap/.metadata | 10 + packages/morse_tap/CHANGELOG.md | 3 + packages/morse_tap/LICENSE | 1 + packages/morse_tap/README.md | 310 ++++++++ packages/morse_tap/example/.gitignore | 52 ++ packages/morse_tap/example/.metadata | 45 ++ packages/morse_tap/example/README.md | 16 + .../morse_tap/example/analysis_options.yaml | 28 + packages/morse_tap/example/lib/main.dart | 694 ++++++++++++++++++ packages/morse_tap/example/main.dart | 688 +++++++++++++++++ packages/morse_tap/example/pubspec.yaml | 90 +++ packages/morse_tap/lib/morse_tap.dart | 10 + .../morse_tap/lib/src/morse_algorithm.dart | 194 +++++ .../morse_tap/lib/src/morse_extensions.dart | 86 +++ .../lib/src/widgets/morse_tap_detector.dart | 339 +++++++++ .../lib/src/widgets/morse_text_input.dart | 521 +++++++++++++ packages/morse_tap/pubspec.yaml | 54 ++ packages/morse_tap/test/morse_tap_test.dart | 57 ++ 19 files changed, 3229 insertions(+) create mode 100644 packages/morse_tap/.gitignore create mode 100644 packages/morse_tap/.metadata create mode 100644 packages/morse_tap/CHANGELOG.md create mode 100644 packages/morse_tap/LICENSE create mode 100644 packages/morse_tap/README.md create mode 100644 packages/morse_tap/example/.gitignore create mode 100644 packages/morse_tap/example/.metadata create mode 100644 packages/morse_tap/example/README.md create mode 100644 packages/morse_tap/example/analysis_options.yaml create mode 100644 packages/morse_tap/example/lib/main.dart create mode 100644 packages/morse_tap/example/main.dart create mode 100644 packages/morse_tap/example/pubspec.yaml create mode 100644 packages/morse_tap/lib/morse_tap.dart create mode 100644 packages/morse_tap/lib/src/morse_algorithm.dart create mode 100644 packages/morse_tap/lib/src/morse_extensions.dart create mode 100644 packages/morse_tap/lib/src/widgets/morse_tap_detector.dart create mode 100644 packages/morse_tap/lib/src/widgets/morse_text_input.dart create mode 100644 packages/morse_tap/pubspec.yaml create mode 100644 packages/morse_tap/test/morse_tap_test.dart diff --git a/packages/morse_tap/.gitignore b/packages/morse_tap/.gitignore new file mode 100644 index 0000000..eb6c05c --- /dev/null +++ b/packages/morse_tap/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/packages/morse_tap/.metadata b/packages/morse_tap/.metadata new file mode 100644 index 0000000..8c47ce6 --- /dev/null +++ b/packages/morse_tap/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d7b523b356d15fb81e7d340bbe52b47f93937323" + channel: "stable" + +project_type: package diff --git a/packages/morse_tap/CHANGELOG.md b/packages/morse_tap/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/packages/morse_tap/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/morse_tap/LICENSE b/packages/morse_tap/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/packages/morse_tap/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/morse_tap/README.md b/packages/morse_tap/README.md new file mode 100644 index 0000000..f246d0b --- /dev/null +++ b/packages/morse_tap/README.md @@ -0,0 +1,310 @@ +

+ + Nonstop Logo + +

NonStop

+

Digital Product Development Experts for Startups & Enterprises

+

+ About | + Website +

+

+ +# morse_tap + +[![Build Status](https://img.shields.io/pub/v/morse_tap.svg)](https://github.com/nonstopio/flutter_forge/tree/main/packages/morse_tap) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +A Flutter package that provides Morse code input functionality using intuitive gestures. Create interactive Morse code experiences with single taps for dots, double taps for dashes, and long presses for spaces. + +## Features + +✨ **MorseTapDetector** - Widget that detects specific Morse code patterns using gestures +🎯 **MorseTextInput** - Real-time gesture-to-text conversion widget +🔄 **String Extensions** - Convert any string to/from Morse code +⚡ **Fast Algorithm** - Efficient Morse code conversion with comprehensive character support +🎨 **Intuitive Gestures** - Single tap = dot, double tap = dash, long press = space + +## Requirements + +- Flutter >=3.19.0 +- Dart >=3.3.0 <4.0.0 + +## Installation + +Add to your `pubspec.yaml`: + +```yaml +dependencies: + morse_tap: ^0.0.1 +``` + +Or install via command line: + +```bash +flutter pub add morse_tap +``` + +## Quick Start + +Import the package: + +```dart +import 'package:morse_tap/morse_tap.dart'; +``` + +## Usage Examples + +### 1. MorseTapDetector - Pattern Detection + +Detect when users input a specific Morse code pattern using gestures: + +```dart +MorseTapDetector( + expectedMorseCode: "... --- ...", // SOS pattern + onCorrectSequence: () { + print("SOS detected!"); + // Handle correct sequence + }, + onIncorrectSequence: () { + print("Wrong pattern, try again"); + }, + onDotAdded: () => print("Dot added"), + onDashAdded: () => print("Dash added"), + onSpaceAdded: () => print("Space added"), + child: Container( + width: 200, + height: 200, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(12), + ), + child: const Center( + child: Text( + 'Use Gestures for SOS', + style: TextStyle(color: Colors.white, fontSize: 20), + ), + ), + ), +) +``` + +### 2. MorseTextInput - Real-time Conversion + +Convert tap input to text in real-time: + +```dart +class MorseInputExample extends StatelessWidget { + final TextEditingController controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MorseTextInput( + controller: controller, + autoConvertToText: true, + showMorsePreview: true, + onTextChanged: (text) { + print("Converted text: $text"); + }, + decoration: const InputDecoration( + labelText: 'Tap to input text', + border: OutlineInputBorder(), + ), + ), + // Your converted text appears in the controller + TextField( + controller: controller, + readOnly: true, + decoration: const InputDecoration( + labelText: 'Output', + ), + ), + ], + ); + } +} +``` + +### 3. String Extensions + +Easy string to Morse code conversion: + +```dart +// Convert text to Morse code +String morse = "HELLO WORLD".toMorseCode(); +print(morse); // ".... . .-.. .-.. --- / .-- --- .-. .-.. -.." + +// Convert Morse code back to text +String text = "... --- ...".fromMorseCode(); +print(text); // "SOS" + +// Validate Morse input +bool isValid = "... --- ...".isValidMorseSequence(); +print(isValid); // true + +// Check if string contains only Morse characters +bool isMorseInput = "... abc".isValidMorseInput(); +print(isMorseInput); // false +``` + +## Configuration + +### Timing Configuration + +Customize tap timing thresholds: + +```dart +MorseTapDetector( + expectedMorseCode: "...", + dotThreshold: Duration(milliseconds: 150), // Shorter for dots + dashThreshold: Duration(milliseconds: 400), // Longer for dashes + letterGap: Duration(milliseconds: 600), // Gap between letters + sequenceTimeout: Duration(seconds: 5), // Reset timeout + onTap: () => print("Correct!"), + child: MyButton(), +) +``` + +### Visual Feedback + +Control visual feedback options: + +```dart +MorseTextInput( + controller: controller, + showMorsePreview: true, // Show Morse preview + feedbackColor: Colors.green, // Tap feedback color + tapAreaHeight: 150.0, // Height of tap area + autoConvertToText: false, // Keep as Morse code +) +``` + +## Supported Characters + +The package supports: +- **Letters**: A-Z (26 letters) +- **Numbers**: 0-9 (10 digits) +- **Punctuation**: . , ? ' ! / ( ) & : ; = + - _ " $ @ + +## Morse Code Reference + +| Character | Morse Code | +|-----------|------------| +| A | .- | +| B | -... | +| C | -.-. | +| S | ... | +| O | --- | +| 0 | ----- | +| 1 | .---- | +| 9 | ----. | + +*See the complete mapping in `MorseCodec` class documentation.* + +## Advanced Usage + +### Custom Morse Patterns + +Create custom pattern detection: + +```dart +final customPattern = "HELP".toMorseCode(); + +MorseTapDetector( + expectedMorseCode: customPattern, + onTap: () => showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("Help Requested!"), + content: Text("Someone needs assistance."), + ), + ), + child: EmergencyButton(), +) +``` + +### Multiple Pattern Detection + +Handle different patterns: + +```dart +class MultiPatternDetector extends StatelessWidget { + final Map patterns = { + 'SOS': '... --- ...', + 'OK': '--- -.-', + 'YES': '-.-- . ...', + }; + + @override + Widget build(BuildContext context) { + return Column( + children: patterns.entries.map((entry) { + return MorseTapDetector( + expectedMorseCode: entry.value, + onTap: () => handlePattern(entry.key), + child: PatternButton(label: entry.key), + ); + }).toList(), + ); + } + + void handlePattern(String pattern) { + print("Pattern $pattern detected!"); + } +} +``` + +## Contributing + +We welcome contributions in various forms: + +- Proposing new features or enhancements. +- Reporting and fixing bugs. +- Engaging in discussions to help make decisions. +- Improving documentation, as it is essential. +- Sending Pull Requests is greatly appreciated! + +A big thank you to all our contributors! 🙌 + +

+
+ + contributors + +
+ +--- + +## 🔗 Connect with NonStop + +
+ +**Stay connected and get the latest updates!** + +[![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/nonstop-io) +[![X.com](https://img.shields.io/badge/X-000000?style=for-the-badge&logo=x&logoColor=white)](https://x.com/NonStopio) +[![Instagram](https://img.shields.io/badge/Instagram-E4405F?style=for-the-badge&logo=instagram&logoColor=white)](https://www.instagram.com/nonstopio_technologies/) +[![YouTube](https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://www.youtube.com/@NonStopioTechnology) +[![Email](https://img.shields.io/badge/Email-D14836?style=for-the-badge&logo=gmail&logoColor=white)](mailto:contact@nonstopio.com) + +
+ +--- + +
+ +> ⭐ Star us on [GitHub](https://github.com/nonstopio/flutter_forge) if this helped you! + +
+ +## 📜 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +
+ +> 🎉 [Founded by Ajay Kumar](https://github.com/ProjectAJ14) 🎉** + +
\ No newline at end of file diff --git a/packages/morse_tap/example/.gitignore b/packages/morse_tap/example/.gitignore new file mode 100644 index 0000000..4711f1d --- /dev/null +++ b/packages/morse_tap/example/.gitignore @@ -0,0 +1,52 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +android +ios +linux +macos +web +windows diff --git a/packages/morse_tap/example/.metadata b/packages/morse_tap/example/.metadata new file mode 100644 index 0000000..6a623a4 --- /dev/null +++ b/packages/morse_tap/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d7b523b356d15fb81e7d340bbe52b47f93937323" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + - platform: android + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + - platform: ios + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + - platform: linux + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + - platform: macos + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + - platform: web + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + - platform: windows + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/morse_tap/example/README.md b/packages/morse_tap/example/README.md new file mode 100644 index 0000000..2b3fce4 --- /dev/null +++ b/packages/morse_tap/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/morse_tap/example/analysis_options.yaml b/packages/morse_tap/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/packages/morse_tap/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/morse_tap/example/lib/main.dart b/packages/morse_tap/example/lib/main.dart new file mode 100644 index 0000000..371ca66 --- /dev/null +++ b/packages/morse_tap/example/lib/main.dart @@ -0,0 +1,694 @@ +import 'package:flutter/material.dart'; +import 'package:morse_tap/morse_tap.dart'; + +void main() { + runApp(const MorseTapExampleApp()); +} + +class MorseTapExampleApp extends StatelessWidget { + const MorseTapExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Morse Tap Example', + theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true), + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _currentPage = 0; + final PageController _pageController = PageController(); + + final List _pages = [ + const MorseTapDetectorExample(), + const MorseTextInputExample(), + const StringExtensionExample(), + ]; + + final List _pageTitles = [ + 'Tap Detector', + 'Text Input', + 'String Extensions', + ]; + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_pageTitles[_currentPage]), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _currentPage = index; + }); + }, + children: _pages, + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _currentPage, + onTap: (index) { + setState(() { + _currentPage = index; + }); + _pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.touch_app), + label: 'Tap Detector', + ), + BottomNavigationBarItem( + icon: Icon(Icons.text_fields), + label: 'Text Input', + ), + BottomNavigationBarItem(icon: Icon(Icons.code), label: 'Extensions'), + ], + ), + ); + } +} + +class MorseTapDetectorExample extends StatefulWidget { + const MorseTapDetectorExample({super.key}); + + @override + State createState() => + _MorseTapDetectorExampleState(); +} + +class _MorseTapDetectorExampleState extends State { + String _message = 'Use gestures to input SOS (... --- ...)'; + String _currentTarget = 'SOS'; + Color _buttonColor = Colors.blue; + String _gestureHint = ''; + + final Map _targets = { + 'SOS': '... --- ...', + 'HELLO': '.... . .-.. .-.. ---', + 'OK': '--- -.-', + 'YES': '-.-- . ...', + 'NO': '-. ---', + }; + + void _onCorrectSequence() { + setState(() { + _message = '✅ Perfect! You tapped $_currentTarget correctly!'; + _buttonColor = Colors.green; + _gestureHint = ''; + }); + + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + _message = + 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _buttonColor = Colors.blue; + }); + } + }); + } + + void _onIncorrectSequence() { + setState(() { + _message = '❌ Wrong sequence. Try again!'; + _buttonColor = Colors.red; + _gestureHint = ''; + }); + + Future.delayed(const Duration(seconds: 1), () { + if (mounted) { + setState(() { + _message = + 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _buttonColor = Colors.blue; + }); + } + }); + } + + void _onTimeout() { + setState(() { + _message = '⏰ Sequence timed out. Try again!'; + _buttonColor = Colors.orange; + _gestureHint = ''; + }); + + Future.delayed(const Duration(seconds: 1), () { + if (mounted) { + setState(() { + _message = + 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _buttonColor = Colors.blue; + }); + } + }); + } + + void _onDotAdded() { + setState(() { + _gestureHint = 'Added dot (•)'; + }); + } + + void _onDashAdded() { + setState(() { + _gestureHint = 'Added dash (—)'; + }); + } + + void _onSpaceAdded() { + setState(() { + _gestureHint = 'Added space'; + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Morse Tap Detector', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Tap the button below using Morse code patterns. Short taps are dots (•), long taps are dashes (—).', + ), + const SizedBox(height: 16), + Text( + 'Target: $_currentTarget', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Morse: ${_targets[_currentTarget]}', + style: const TextStyle( + fontSize: 14, + fontFamily: 'monospace', + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Target selection + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _targets.keys.map((target) { + final isSelected = target == _currentTarget; + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: FilterChip( + label: Text(target), + selected: isSelected, + onSelected: (selected) { + if (selected) { + setState(() { + _currentTarget = target; + _message = + 'Use gestures to input $target (${_targets[target]})'; + _buttonColor = Colors.blue; + _gestureHint = ''; + }); + } + }, + ), + ); + }).toList(), + ), + ), + + const SizedBox(height: 24), + + // Morse tap detector + Expanded( + child: MorseTapDetector( + expectedMorseCode: _targets[_currentTarget]!, + onCorrectSequence: _onCorrectSequence, + onIncorrectSequence: _onIncorrectSequence, + onSequenceTimeout: _onTimeout, + onDotAdded: _onDotAdded, + onDashAdded: _onDashAdded, + onSpaceAdded: _onSpaceAdded, + feedbackColor: _buttonColor, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + decoration: BoxDecoration( + color: _buttonColor, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: _buttonColor.withValues(alpha: 0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.touch_app, size: 48, color: Colors.white), + SizedBox(height: 12), + Text( + 'TAP HERE', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + Text( + '1 tap = • | 2 taps = — | Hold = space', + style: TextStyle(color: Colors.white70, fontSize: 14), + ), + ], + ), + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Status message + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Text( + _message, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + if (_gestureHint.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.blue[50], + border: Border.all(color: Colors.blue[200]!), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + _gestureHint, + style: TextStyle( + fontSize: 12, + color: Colors.blue[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], + ), + ), + ], + ), + ); + } +} + +class MorseTextInputExample extends StatefulWidget { + const MorseTextInputExample({super.key}); + + @override + State createState() => _MorseTextInputExampleState(); +} + +class _MorseTextInputExampleState extends State { + final TextEditingController _textController = TextEditingController(); + final TextEditingController _morseController = TextEditingController(); + bool _autoConvert = true; + + @override + void dispose() { + _textController.dispose(); + _morseController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Morse Text Input', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Tap in the input area to create Morse code. The widget converts your taps to text in real-time.', + ), + const SizedBox(height: 12), + Row( + children: [ + Checkbox( + value: _autoConvert, + onChanged: (value) { + setState(() { + _autoConvert = value ?? true; + }); + }, + ), + const Text('Auto-convert to text'), + ], + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Text input mode + Expanded( + child: MorseTextInput( + controller: _autoConvert ? _textController : _morseController, + autoConvertToText: _autoConvert, + showMorsePreview: true, + onTextChanged: (text) { + // Optional: handle text changes + }, + decoration: InputDecoration( + labelText: _autoConvert ? 'Converted Text' : 'Morse Code', + helperText: _autoConvert + ? 'Text appears here as you tap' + : 'Raw Morse code appears here', + ), + ), + ), + + const SizedBox(height: 16), + + // Instructions + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Instructions:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text('• Single tap = dot (•)'), + const Text('• Double tap = dash (—)'), + const Text('• Long press = space between letters'), + const Text('• Auto-completion after 1.2 seconds'), + const SizedBox(height: 12), + const Text( + 'Try tapping "HELLO" or "SOS"!', + style: TextStyle( + fontWeight: FontWeight.w500, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +class StringExtensionExample extends StatefulWidget { + const StringExtensionExample({super.key}); + + @override + State createState() => _StringExtensionExampleState(); +} + +class _StringExtensionExampleState extends State { + final TextEditingController _inputController = TextEditingController( + text: 'HELLO WORLD', + ); + String _morseOutput = ''; + String _backToText = ''; + + @override + void initState() { + super.initState(); + _updateOutputs(); + _inputController.addListener(_updateOutputs); + } + + @override + void dispose() { + _inputController.dispose(); + super.dispose(); + } + + void _updateOutputs() { + final input = _inputController.text; + setState(() { + _morseOutput = input.toMorseCode(); + _backToText = _morseOutput.fromMorseCode(); + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'String Extensions', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + 'Demonstrates the extension methods available on String for Morse code conversion.', + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Input field + TextField( + controller: _inputController, + decoration: const InputDecoration( + labelText: 'Input Text', + border: OutlineInputBorder(), + helperText: 'Type any text to see the Morse code conversion', + ), + textCapitalization: TextCapitalization.characters, + ), + + const SizedBox(height: 16), + + // Outputs + Expanded( + child: Column( + children: [ + // Morse code output + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.code, size: 16), + const SizedBox(width: 8), + const Text( + '.toMorseCode()', + style: TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText( + _morseOutput.isEmpty + ? 'Enter text above...' + : _morseOutput, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 12), + + // Back to text output + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.text_fields, size: 16), + const SizedBox(width: 8), + const Text( + '.fromMorseCode()', + style: TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText( + _backToText.isEmpty + ? 'Converted text appears here...' + : _backToText, + style: const TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 12), + + // Validation indicators + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Validation Methods:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + _inputController.text.isValidMorseInput() + ? Icons.check_circle + : Icons.cancel, + color: _inputController.text.isValidMorseInput() + ? Colors.green + : Colors.red, + size: 16, + ), + const SizedBox(width: 8), + const Text('.isValidMorseInput()'), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Icon( + _morseOutput.isValidMorseSequence() + ? Icons.check_circle + : Icons.cancel, + color: _morseOutput.isValidMorseSequence() + ? Colors.green + : Colors.red, + size: 16, + ), + const SizedBox(width: 8), + const Text('.isValidMorseSequence()'), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/packages/morse_tap/example/main.dart b/packages/morse_tap/example/main.dart new file mode 100644 index 0000000..07153fb --- /dev/null +++ b/packages/morse_tap/example/main.dart @@ -0,0 +1,688 @@ +import 'package:flutter/material.dart'; +import 'package:morse_tap/morse_tap.dart'; + +void main() { + runApp(const MorseTapExampleApp()); +} + +class MorseTapExampleApp extends StatelessWidget { + const MorseTapExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Morse Tap Example', + theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true), + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _currentPage = 0; + final PageController _pageController = PageController(); + + final List _pages = [ + const MorseTapDetectorExample(), + const MorseTextInputExample(), + const StringExtensionExample(), + ]; + + final List _pageTitles = [ + 'Tap Detector', + 'Text Input', + 'String Extensions', + ]; + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_pageTitles[_currentPage]), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _currentPage = index; + }); + }, + children: _pages, + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _currentPage, + onTap: (index) { + setState(() { + _currentPage = index; + }); + _pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.touch_app), + label: 'Tap Detector', + ), + BottomNavigationBarItem( + icon: Icon(Icons.text_fields), + label: 'Text Input', + ), + BottomNavigationBarItem(icon: Icon(Icons.code), label: 'Extensions'), + ], + ), + ); + } +} + +class MorseTapDetectorExample extends StatefulWidget { + const MorseTapDetectorExample({super.key}); + + @override + State createState() => + _MorseTapDetectorExampleState(); +} + +class _MorseTapDetectorExampleState extends State { + String _message = 'Use gestures to input SOS (... --- ...)'; + String _currentTarget = 'SOS'; + Color _buttonColor = Colors.blue; + String _gestureHint = ''; + + final Map _targets = { + 'SOS': '... --- ...', + 'HELLO': '.... . .-.. .-.. ---', + 'OK': '--- -.-', + 'YES': '-.-- . ...', + 'NO': '-. ---', + }; + + void _onCorrectSequence() { + setState(() { + _message = '✅ Perfect! You tapped $_currentTarget correctly!'; + _buttonColor = Colors.green; + _gestureHint = ''; + }); + + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + _message = 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _buttonColor = Colors.blue; + }); + } + }); + } + + void _onIncorrectSequence() { + setState(() { + _message = '❌ Wrong sequence. Try again!'; + _buttonColor = Colors.red; + _gestureHint = ''; + }); + + Future.delayed(const Duration(seconds: 1), () { + if (mounted) { + setState(() { + _message = 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _buttonColor = Colors.blue; + }); + } + }); + } + + void _onTimeout() { + setState(() { + _message = '⏰ Sequence timed out. Try again!'; + _buttonColor = Colors.orange; + _gestureHint = ''; + }); + + Future.delayed(const Duration(seconds: 1), () { + if (mounted) { + setState(() { + _message = 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _buttonColor = Colors.blue; + }); + } + }); + } + + void _onDotAdded() { + setState(() { + _gestureHint = 'Added dot (•)'; + }); + } + + void _onDashAdded() { + setState(() { + _gestureHint = 'Added dash (—)'; + }); + } + + void _onSpaceAdded() { + setState(() { + _gestureHint = 'Added space'; + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Morse Tap Detector', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Tap the button below using Morse code patterns. Short taps are dots (•), long taps are dashes (—).', + ), + const SizedBox(height: 16), + Text( + 'Target: $_currentTarget', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Morse: ${_targets[_currentTarget]}', + style: const TextStyle( + fontSize: 14, + fontFamily: 'monospace', + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Target selection + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _targets.keys.map((target) { + final isSelected = target == _currentTarget; + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: FilterChip( + label: Text(target), + selected: isSelected, + onSelected: (selected) { + if (selected) { + setState(() { + _currentTarget = target; + _message = + 'Use gestures to input $target (${_targets[target]})'; + _buttonColor = Colors.blue; + _gestureHint = ''; + }); + } + }, + ), + ); + }).toList(), + ), + ), + + const SizedBox(height: 24), + + // Morse tap detector + Expanded( + child: MorseTapDetector( + expectedMorseCode: _targets[_currentTarget]!, + onCorrectSequence: _onCorrectSequence, + onIncorrectSequence: _onIncorrectSequence, + onSequenceTimeout: _onTimeout, + onDotAdded: _onDotAdded, + onDashAdded: _onDashAdded, + onSpaceAdded: _onSpaceAdded, + feedbackColor: _buttonColor, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + decoration: BoxDecoration( + color: _buttonColor, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: _buttonColor.withValues(alpha: 0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.touch_app, size: 48, color: Colors.white), + SizedBox(height: 12), + Text( + 'TAP HERE', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + Text( + '1 tap = • | 2 taps = — | Hold = space', + style: TextStyle(color: Colors.white70, fontSize: 14), + ), + ], + ), + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Status message + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Text( + _message, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + if (_gestureHint.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue[50], + border: Border.all(color: Colors.blue[200]!), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + _gestureHint, + style: TextStyle( + fontSize: 12, + color: Colors.blue[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], + ), + ), + ], + ), + ); + } +} + +class MorseTextInputExample extends StatefulWidget { + const MorseTextInputExample({super.key}); + + @override + State createState() => _MorseTextInputExampleState(); +} + +class _MorseTextInputExampleState extends State { + final TextEditingController _textController = TextEditingController(); + final TextEditingController _morseController = TextEditingController(); + bool _autoConvert = true; + + @override + void dispose() { + _textController.dispose(); + _morseController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Morse Text Input', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Tap in the input area to create Morse code. The widget converts your taps to text in real-time.', + ), + const SizedBox(height: 12), + Row( + children: [ + Checkbox( + value: _autoConvert, + onChanged: (value) { + setState(() { + _autoConvert = value ?? true; + }); + }, + ), + const Text('Auto-convert to text'), + ], + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Text input mode + Expanded( + child: MorseTextInput( + controller: _autoConvert ? _textController : _morseController, + autoConvertToText: _autoConvert, + showMorsePreview: true, + onTextChanged: (text) { + // Optional: handle text changes + }, + decoration: InputDecoration( + labelText: _autoConvert ? 'Converted Text' : 'Morse Code', + helperText: _autoConvert + ? 'Text appears here as you tap' + : 'Raw Morse code appears here', + ), + ), + ), + + const SizedBox(height: 16), + + // Instructions + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Instructions:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text('• Single tap = dot (•)'), + const Text('• Double tap = dash (—)'), + const Text('• Long press = space between letters'), + const Text('• Auto-completion after 1.2 seconds'), + const SizedBox(height: 12), + const Text( + 'Try tapping "HELLO" or "SOS"!', + style: TextStyle( + fontWeight: FontWeight.w500, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +class StringExtensionExample extends StatefulWidget { + const StringExtensionExample({super.key}); + + @override + State createState() => _StringExtensionExampleState(); +} + +class _StringExtensionExampleState extends State { + final TextEditingController _inputController = TextEditingController( + text: 'HELLO WORLD', + ); + String _morseOutput = ''; + String _backToText = ''; + + @override + void initState() { + super.initState(); + _updateOutputs(); + _inputController.addListener(_updateOutputs); + } + + @override + void dispose() { + _inputController.dispose(); + super.dispose(); + } + + void _updateOutputs() { + final input = _inputController.text; + setState(() { + _morseOutput = input.toMorseCode(); + _backToText = _morseOutput.fromMorseCode(); + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'String Extensions', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + 'Demonstrates the extension methods available on String for Morse code conversion.', + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Input field + TextField( + controller: _inputController, + decoration: const InputDecoration( + labelText: 'Input Text', + border: OutlineInputBorder(), + helperText: 'Type any text to see the Morse code conversion', + ), + textCapitalization: TextCapitalization.characters, + ), + + const SizedBox(height: 16), + + // Outputs + Expanded( + child: Column( + children: [ + // Morse code output + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.code, size: 16), + const SizedBox(width: 8), + const Text( + '.toMorseCode()', + style: TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText( + _morseOutput.isEmpty + ? 'Enter text above...' + : _morseOutput, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 12), + + // Back to text output + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.text_fields, size: 16), + const SizedBox(width: 8), + const Text( + '.fromMorseCode()', + style: TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText( + _backToText.isEmpty + ? 'Converted text appears here...' + : _backToText, + style: const TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 12), + + // Validation indicators + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Validation Methods:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + _inputController.text.isValidMorseInput() + ? Icons.check_circle + : Icons.cancel, + color: _inputController.text.isValidMorseInput() + ? Colors.green + : Colors.red, + size: 16, + ), + const SizedBox(width: 8), + const Text('.isValidMorseInput()'), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Icon( + _morseOutput.isValidMorseSequence() + ? Icons.check_circle + : Icons.cancel, + color: _morseOutput.isValidMorseSequence() + ? Colors.green + : Colors.red, + size: 16, + ), + const SizedBox(width: 8), + const Text('.isValidMorseSequence()'), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/packages/morse_tap/example/pubspec.yaml b/packages/morse_tap/example/pubspec.yaml new file mode 100644 index 0000000..b321ebd --- /dev/null +++ b/packages/morse_tap/example/pubspec.yaml @@ -0,0 +1,90 @@ +name: morse_tap_example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.8.1 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + morse_tap: + path: ../ +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/packages/morse_tap/lib/morse_tap.dart b/packages/morse_tap/lib/morse_tap.dart new file mode 100644 index 0000000..8d4e3e0 --- /dev/null +++ b/packages/morse_tap/lib/morse_tap.dart @@ -0,0 +1,10 @@ +/// A Flutter package that provides Morse code input functionality through tap-based widgets. +/// +/// This library includes widgets for detecting Morse code tap patterns, converting +/// tap input to text, and utility extensions for working with Morse code. +library; + +export 'src/morse_algorithm.dart'; +export 'src/morse_extensions.dart'; +export 'src/widgets/morse_tap_detector.dart'; +export 'src/widgets/morse_text_input.dart'; diff --git a/packages/morse_tap/lib/src/morse_algorithm.dart b/packages/morse_tap/lib/src/morse_algorithm.dart new file mode 100644 index 0000000..654ca37 --- /dev/null +++ b/packages/morse_tap/lib/src/morse_algorithm.dart @@ -0,0 +1,194 @@ +/// Core algorithm for Morse code conversion and validation. +class MorseCodec { + /// Character mappings from text to Morse code + static const Map _textToMorse = { + 'A': '.-', + 'B': '-...', + 'C': '-.-.', + 'D': '-..', + 'E': '.', + 'F': '..-.', + 'G': '--.', + 'H': '....', + 'I': '..', + 'J': '.---', + 'K': '-.-', + 'L': '.-..', + 'M': '--', + 'N': '-.', + 'O': '---', + 'P': '.--.', + 'Q': '--.-', + 'R': '.-.', + 'S': '...', + 'T': '-', + 'U': '..-', + 'V': '...-', + 'W': '.--', + 'X': '-..-', + 'Y': '-.--', + 'Z': '--..', + '0': '-----', + '1': '.----', + '2': '..---', + '3': '...--', + '4': '....-', + '5': '.....', + '6': '-....', + '7': '--...', + '8': '---..', + '9': '----.', + '.': '.-.-.-', + ',': '--..--', + '?': '..--..', + "'": '.----.', + '!': '-.-.--', + '/': '-..-.', + '(': '-.--.', + ')': '-.--.-', + '&': '.-...', + ':': '---...', + ';': '-.-.-.', + '=': '-...-', + '+': '.-.-.', + '-': '-....-', + '_': '..--.-', + '"': '.-..-.', + '\$': '...-..-', + '@': '.--.-.', + }; + + /// Reverse mapping for Morse code to text conversion + static final Map _morseToText = { + for (final entry in _textToMorse.entries) entry.value: entry.key, + }; + + /// Converts text to Morse code representation + /// + /// [text] The input text to convert + /// Returns Morse code string with spaces between letters and " / " between words + static String textToMorse(String text) { + if (text.isEmpty) return ''; + + final words = text.toUpperCase().split(' '); + final morseWords = []; + + for (final word in words) { + if (word.isEmpty) continue; + + final morseLetters = []; + for (int i = 0; i < word.length; i++) { + final char = word[i]; + final morse = _textToMorse[char]; + if (morse != null) { + morseLetters.add(morse); + } + } + + if (morseLetters.isNotEmpty) { + morseWords.add(morseLetters.join(' ')); + } + } + + return morseWords.join(' / '); + } + + /// Converts Morse code to text + /// + /// [morse] The Morse code string to convert + /// Returns the decoded text + static String morseToText(String morse) { + if (morse.isEmpty) return ''; + + final words = morse.split(' / '); + final textWords = []; + + for (final word in words) { + if (word.trim().isEmpty) continue; + + final letters = word.trim().split(' '); + final textLetters = []; + + for (final letter in letters) { + if (letter.isEmpty) continue; + final char = _morseToText[letter]; + if (char != null) { + textLetters.add(char); + } + } + + if (textLetters.isNotEmpty) { + textWords.add(textLetters.join()); + } + } + + return textWords.join(' '); + } + + /// Validates if a Morse code sequence is valid + /// + /// [sequence] The Morse code sequence to validate + /// Returns true if all characters in the sequence are valid Morse codes + static bool isValidMorseSequence(String sequence) { + if (sequence.isEmpty) return true; + + // Split by word separators first + final words = sequence.split(' / '); + + for (final word in words) { + if (word.trim().isEmpty) continue; + + // Split by letter separators + final letters = word.trim().split(' '); + + for (final letter in letters) { + if (letter.isEmpty) continue; + + // Check if this morse code exists in our mapping + if (!_morseToText.containsKey(letter)) { + return false; + } + } + } + + return true; + } + + /// Gets the relative duration for a Morse character + /// + /// [morseChar] Single Morse character (. or -) + /// Returns duration in milliseconds (dot = 100ms, dash = 300ms) + static Duration getCharacterDuration(String morseChar) { + switch (morseChar) { + case '.': + return const Duration(milliseconds: 100); + case '-': + return const Duration(milliseconds: 300); + default: + return const Duration(milliseconds: 0); + } + } + + /// Splits a Morse code sequence into individual characters + /// + /// [sequence] The Morse code sequence to split + /// Returns list of individual Morse characters (dots and dashes) + static List splitMorseSequence(String sequence) { + final characters = []; + + for (int i = 0; i < sequence.length; i++) { + final char = sequence[i]; + if (char == '.' || char == '-') { + characters.add(char); + } + } + + return characters; + } + + /// Gets all supported characters + static Set get supportedCharacters => _textToMorse.keys.toSet(); + + /// Gets all Morse codes + static Set get supportedMorseCodes => _morseToText.keys.toSet(); +} diff --git a/packages/morse_tap/lib/src/morse_extensions.dart b/packages/morse_tap/lib/src/morse_extensions.dart new file mode 100644 index 0000000..7f0a6d2 --- /dev/null +++ b/packages/morse_tap/lib/src/morse_extensions.dart @@ -0,0 +1,86 @@ +import 'morse_algorithm.dart'; + +/// Extension methods for converting strings to Morse code +extension StringToMorse on String { + /// Converts this string to Morse code representation + /// + /// Returns a string with dots and dashes representing the Morse code. + /// Spaces separate letters, and " / " separates words. + /// + /// Example: + /// ```dart + /// "HELLO".toMorseCode(); // Returns: ".... . .-.. .-.. ---" + /// "SOS".toMorseCode(); // Returns: "... --- ..." + /// ``` + String toMorseCode() { + return MorseCodec.textToMorse(this); + } + + /// Converts this string to Morse code with timing indicators + /// + /// Returns a formatted string that includes timing information for + /// dots, dashes, and pauses between letters and words. + /// + /// Format: + /// - . = dot (100ms) + /// - - = dash (300ms) + /// - (space) = letter separator (200ms pause) + /// - / = word separator (700ms pause) + /// + /// Example: + /// ```dart + /// "HI".toMorseCodeWithTiming(); // Returns: ".... .. (dot=100ms, dash=300ms)" + /// ``` + String toMorseCodeWithTiming() { + final morse = toMorseCode(); + if (morse.isEmpty) return ''; + + return '$morse (dot=100ms, dash=300ms, letter_gap=200ms, word_gap=700ms)'; + } + + /// Validates if this string contains only valid Morse code input characters + /// + /// Returns true if the string contains only dots (.), dashes (-), spaces, + /// and forward slashes (/) which are valid Morse code characters. + /// + /// Example: + /// ```dart + /// "... --- ...".isValidMorseInput(); // Returns: true + /// "... abc ...".isValidMorseInput(); // Returns: false + /// ``` + bool isValidMorseInput() { + if (isEmpty) return true; + + // Check if string contains only valid Morse characters: ., -, space, / + final validPattern = RegExp(r'^[.\-\s/]*$'); + return validPattern.hasMatch(this); + } + + /// Converts Morse code string back to text + /// + /// If this string is a valid Morse code sequence, it will be converted + /// back to readable text. Invalid sequences will return an empty string. + /// + /// Example: + /// ```dart + /// "... --- ...".fromMorseCode(); // Returns: "SOS" + /// ".... . .-.. .-.. ---".fromMorseCode(); // Returns: "HELLO" + /// ``` + String fromMorseCode() { + return MorseCodec.morseToText(this); + } + + /// Checks if this string represents a valid Morse code sequence + /// + /// Returns true if all Morse codes in the string are valid and can be + /// converted back to text. + /// + /// Example: + /// ```dart + /// "... --- ...".isValidMorseSequence(); // Returns: true + /// "... xyz ...".isValidMorseSequence(); // Returns: false + /// ``` + bool isValidMorseSequence() { + return MorseCodec.isValidMorseSequence(this); + } +} diff --git a/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart b/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart new file mode 100644 index 0000000..7bdd450 --- /dev/null +++ b/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart @@ -0,0 +1,339 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; + +/// A widget that detects Morse code tap patterns and triggers callbacks +/// when the correct sequence is tapped. +/// +/// The widget uses discrete gestures: +/// - Single tap (onTap) = dot (.) +/// - Double tap (onDoubleTap) = dash (-) +/// - Long press (onLongPress) = space between letters +/// +/// It validates the tapped sequence against the expected Morse code pattern +/// and only triggers [onCorrectSequence] when the correct sequence is completed. +class MorseTapDetector extends StatefulWidget { + /// Creates a Morse tap detector widget. + /// + /// [expectedMorseCode] The Morse code sequence that should be tapped + /// [onCorrectSequence] Callback triggered when correct sequence is completed + /// [child] The child widget to wrap + /// [sequenceTimeout] Timeout for incomplete sequences + /// [showVisualFeedback] Whether to show visual feedback during input + /// [feedbackColor] Color for visual feedback + const MorseTapDetector({ + super.key, + required this.expectedMorseCode, + required this.onCorrectSequence, + required this.child, + this.sequenceTimeout = const Duration(seconds: 10), + this.showVisualFeedback = true, + this.feedbackColor = Colors.blue, + this.onIncorrectSequence, + this.onSequenceTimeout, + this.onDotAdded, + this.onDashAdded, + this.onSpaceAdded, + }); + + /// The expected Morse code sequence (e.g., "... --- ..." for SOS) + final String expectedMorseCode; + + /// Callback triggered when the correct sequence is detected + final VoidCallback onCorrectSequence; + + /// The child widget to detect taps on + final Widget child; + + /// Timeout duration for incomplete sequences + final Duration sequenceTimeout; + + /// Whether to show visual feedback during tap input + final bool showVisualFeedback; + + /// Color for visual feedback overlay + final Color feedbackColor; + + /// Callback for when an incorrect sequence is detected + final VoidCallback? onIncorrectSequence; + + /// Callback for when a sequence times out + final VoidCallback? onSequenceTimeout; + + /// Callback when a dot is added + final VoidCallback? onDotAdded; + + /// Callback when a dash is added + final VoidCallback? onDashAdded; + + /// Callback when a space is added + final VoidCallback? onSpaceAdded; + + @override + State createState() => _MorseTapDetectorState(); +} + +class _MorseTapDetectorState extends State + with TickerProviderStateMixin { + final List _currentSequence = []; + Timer? _timeoutTimer; + + late AnimationController _feedbackController; + + late AnimationController _dotController; + late AnimationController _dashController; + late AnimationController _spaceController; + + @override + void initState() { + super.initState(); + + // Feedback animation controller (unused but kept for potential future use) + _feedbackController = AnimationController( + duration: const Duration(milliseconds: 100), + vsync: this, + ); + + // Individual gesture feedback animations + _dotController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + + _dashController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _spaceController = AnimationController( + duration: const Duration(milliseconds: 150), + vsync: this, + ); + } + + @override + void dispose() { + _timeoutTimer?.cancel(); + _feedbackController.dispose(); + _dotController.dispose(); + _dashController.dispose(); + _spaceController.dispose(); + super.dispose(); + } + + void _startSequenceTimeout() { + _timeoutTimer?.cancel(); + _timeoutTimer = Timer(widget.sequenceTimeout, () { + _resetSequence(); + widget.onSequenceTimeout?.call(); + }); + } + + void _resetSequence() { + _currentSequence.clear(); + _timeoutTimer?.cancel(); + if (mounted) { + setState(() {}); + } + } + + void _addMorseCharacter(String character) { + _currentSequence.add(character); + _startSequenceTimeout(); + setState(() {}); + + // Check if sequence matches expected pattern + _checkSequence(); + } + + void _onSingleTap() { + // Single tap = dot + _addMorseCharacter('.'); + widget.onDotAdded?.call(); + + // Visual feedback + if (widget.showVisualFeedback) { + _dotController.forward().then((_) => _dotController.reverse()); + } + } + + void _onDoubleTap() { + // Double tap = dash + _addMorseCharacter('-'); + widget.onDashAdded?.call(); + + // Visual feedback + if (widget.showVisualFeedback) { + _dashController.forward().then((_) => _dashController.reverse()); + } + } + + void _onLongPress() { + // Long press = space (letter separator) + _addMorseCharacter(' '); + widget.onSpaceAdded?.call(); + + // Visual feedback + if (widget.showVisualFeedback) { + _spaceController.forward().then((_) => _spaceController.reverse()); + } + } + + void _checkSequence() { + final currentMorse = _currentSequence.join(''); + final expectedMorse = widget.expectedMorseCode; + + if (currentMorse == expectedMorse) { + // Correct sequence detected! + _resetSequence(); + widget.onCorrectSequence(); + } else if (currentMorse.length >= expectedMorse.length || + !expectedMorse.startsWith(currentMorse)) { + // Sequence is wrong or too long + _resetSequence(); + widget.onIncorrectSequence?.call(); + } + // Otherwise, continue waiting for more input + } + + String get _currentMorseDisplay { + return _currentSequence.join(''); + } + + Color _getCurrentFeedbackColor() { + if (_dotController.isAnimating) { + return Colors.green; + } else if (_dashController.isAnimating) { + return Colors.orange; + } else if (_spaceController.isAnimating) { + return Colors.purple; + } + return widget.feedbackColor; + } + + double _getCurrentFeedbackValue() { + if (_dotController.isAnimating) { + return _dotController.value * 0.3; + } else if (_dashController.isAnimating) { + return _dashController.value * 0.3; + } else if (_spaceController.isAnimating) { + return _spaceController.value * 0.3; + } + return 0.0; + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _onSingleTap, + onDoubleTap: _onDoubleTap, + onLongPress: _onLongPress, + child: AnimatedBuilder( + animation: Listenable.merge([ + _dotController, + _dashController, + _spaceController, + ]), + builder: (context, child) { + return Stack( + children: [ + // Base widget with feedback overlay + Container( + decoration: BoxDecoration( + color: widget.showVisualFeedback + ? _getCurrentFeedbackColor().withValues( + alpha: _getCurrentFeedbackValue(), + ) + : null, + borderRadius: BorderRadius.circular(8), + ), + child: widget.child, + ), + + // Current sequence display + if (_currentSequence.isNotEmpty) + Positioned( + top: 8, + right: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.white24), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.radio_button_checked, + color: Colors.blue[300], + size: 12, + ), + const SizedBox(width: 6), + Text( + _currentMorseDisplay, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'monospace', + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + + // Gesture hints overlay + if (_currentSequence.isEmpty) + Positioned( + bottom: 8, + left: 8, + right: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(12), + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + '1 tap = •', + style: TextStyle( + color: Colors.white70, + fontSize: 10, + ), + ), + Text( + '2 taps = —', + style: TextStyle( + color: Colors.white70, + fontSize: 10, + ), + ), + Text( + 'Hold = space', + style: TextStyle( + color: Colors.white70, + fontSize: 10, + ), + ), + ], + ), + ), + ), + ], + ); + }, + ), + ); + } +} \ No newline at end of file diff --git a/packages/morse_tap/lib/src/widgets/morse_text_input.dart b/packages/morse_tap/lib/src/widgets/morse_text_input.dart new file mode 100644 index 0000000..63b60de --- /dev/null +++ b/packages/morse_tap/lib/src/widgets/morse_text_input.dart @@ -0,0 +1,521 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import '../morse_algorithm.dart'; + +/// A widget that converts tap input to Morse code and updates a text controller +/// or calls a method in real-time. +/// +/// This widget provides a complete Morse code input experience using gestures: +/// - Single tap = dot (.) +/// - Double tap = dash (-) +/// - Long press = space between letters +/// +/// It converts Morse patterns into readable text automatically or provides raw Morse code. +class MorseTextInput extends StatefulWidget { + /// Creates a Morse text input widget. + /// + /// Either [controller] or [onTextChanged] must be provided. + /// + /// [controller] Text editing controller to update with converted text + /// [onTextChanged] Callback for text changes + /// [letterGap] Pause duration between letters for auto-completion + /// [wordGap] Pause duration between words for auto-completion + /// [showMorsePreview] Whether to show Morse code preview + /// [autoConvertToText] Whether to auto-convert Morse to readable text + /// [onClear] Callback when input is cleared + /// [decoration] Input decoration for the text field + const MorseTextInput({ + super.key, + this.controller, + this.onTextChanged, + this.letterGap = const Duration(milliseconds: 1200), + this.wordGap = const Duration(seconds: 3), + this.showMorsePreview = true, + this.autoConvertToText = true, + this.onClear, + this.decoration, + this.tapAreaHeight = 120.0, + this.feedbackColor = Colors.blue, + }) : assert( + controller != null || onTextChanged != null, + 'Either controller or onTextChanged must be provided', + ); + + /// Text editing controller to update with converted text + final TextEditingController? controller; + + /// Callback for text changes + final ValueChanged? onTextChanged; + + /// Pause duration between letters for auto letter completion + final Duration letterGap; + + /// Pause duration between words for auto word completion + final Duration wordGap; + + /// Whether to show Morse code preview above the input + final bool showMorsePreview; + + /// Whether to automatically convert Morse code to readable text + final bool autoConvertToText; + + /// Callback when input is cleared + final VoidCallback? onClear; + + /// Input decoration for the text field display + final InputDecoration? decoration; + + /// Height of the tap detection area + final double tapAreaHeight; + + /// Color for tap feedback + final Color feedbackColor; + + @override + State createState() => _MorseTextInputState(); +} + +class _MorseTextInputState extends State + with TickerProviderStateMixin { + late final TextEditingController _internalController; + final List _currentLetter = []; + final List _morseWords = []; + final List _morseLetters = []; + + Timer? _letterGapTimer; + Timer? _wordGapTimer; + + late AnimationController _dotController; + late AnimationController _dashController; + late AnimationController _spaceController; + + @override + void initState() { + super.initState(); + _internalController = widget.controller ?? TextEditingController(); + + // Animation controllers for visual feedback + _dotController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + + _dashController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _spaceController = AnimationController( + duration: const Duration(milliseconds: 150), + vsync: this, + ); + } + + @override + void dispose() { + _letterGapTimer?.cancel(); + _wordGapTimer?.cancel(); + _dotController.dispose(); + _dashController.dispose(); + _spaceController.dispose(); + if (widget.controller == null) { + _internalController.dispose(); + } + super.dispose(); + } + + void _onSingleTap() { + // Single tap = dot + _currentLetter.add('.'); + _dotController.forward().then((_) => _dotController.reverse()); + + // Cancel pending timers and start letter gap timer + _letterGapTimer?.cancel(); + _wordGapTimer?.cancel(); + + _letterGapTimer = Timer(widget.letterGap, () { + _completeLetter(); + }); + + setState(() {}); + } + + void _onDoubleTap() { + // Double tap = dash + _currentLetter.add('-'); + _dashController.forward().then((_) => _dashController.reverse()); + + // Cancel pending timers and start letter gap timer + _letterGapTimer?.cancel(); + _wordGapTimer?.cancel(); + + _letterGapTimer = Timer(widget.letterGap, () { + _completeLetter(); + }); + + setState(() {}); + } + + void _onLongPress() { + // Long press = complete current letter and add space + _spaceController.forward().then((_) => _spaceController.reverse()); + + _letterGapTimer?.cancel(); + _wordGapTimer?.cancel(); + + if (_currentLetter.isNotEmpty) { + _completeLetter(); + } + + // Force word completion after a short delay to allow letter to process + Timer(const Duration(milliseconds: 100), () { + _completeWord(); + }); + } + + void _completeLetter() { + if (_currentLetter.isNotEmpty) { + final letterMorse = _currentLetter.join(''); + _morseLetters.add(letterMorse); + _currentLetter.clear(); + + // Start word gap timer + _wordGapTimer = Timer(widget.wordGap, () { + _completeWord(); + }); + + _updateOutput(); + setState(() {}); + } + } + + void _completeWord() { + if (_morseLetters.isNotEmpty) { + final wordMorse = _morseLetters.join(' '); + _morseWords.add(wordMorse); + _morseLetters.clear(); + + _updateOutput(); + setState(() {}); + } + } + + void _updateOutput() { + final currentMorse = _getCurrentMorseCode(); + + if (widget.autoConvertToText) { + final text = MorseCodec.morseToText(currentMorse); + _internalController.text = text; + widget.onTextChanged?.call(text); + } else { + _internalController.text = currentMorse; + widget.onTextChanged?.call(currentMorse); + } + } + + String _getCurrentMorseCode() { + final words = []; + + // Add completed words + words.addAll(_morseWords); + + // Add current word in progress + if (_morseLetters.isNotEmpty || _currentLetter.isNotEmpty) { + final currentWordLetters = []; + currentWordLetters.addAll(_morseLetters); + + // Add current letter in progress + if (_currentLetter.isNotEmpty) { + currentWordLetters.add(_currentLetter.join('')); + } + + if (currentWordLetters.isNotEmpty) { + words.add(currentWordLetters.join(' ')); + } + } + + return words.join(' / '); + } + + String _getCurrentLetterPreview() { + if (_currentLetter.isNotEmpty) { + final letterMorse = _currentLetter.join(''); + final possibleChar = MorseCodec.morseToText(letterMorse); + if (possibleChar.isNotEmpty) { + return '$letterMorse → $possibleChar'; + } + return letterMorse; + } + return ''; + } + + void _clearInput() { + _currentLetter.clear(); + _morseWords.clear(); + _morseLetters.clear(); + _letterGapTimer?.cancel(); + _wordGapTimer?.cancel(); + + _internalController.clear(); + widget.onTextChanged?.call(''); + widget.onClear?.call(); + + setState(() {}); + } + + Color _getCurrentFeedbackColor() { + if (_dotController.isAnimating) { + return Colors.green; + } else if (_dashController.isAnimating) { + return Colors.orange; + } else if (_spaceController.isAnimating) { + return Colors.purple; + } + return widget.feedbackColor; + } + + double _getCurrentFeedbackValue() { + if (_dotController.isAnimating) { + return _dotController.value * 0.15; + } else if (_dashController.isAnimating) { + return _dashController.value * 0.15; + } else if (_spaceController.isAnimating) { + return _spaceController.value * 0.15; + } + return 0.02; + } + + @override + Widget build(BuildContext context) { + final currentMorse = _getCurrentMorseCode(); + final letterPreview = _getCurrentLetterPreview(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Morse preview (optional) + if (widget.showMorsePreview) ...[ + Container( + height: 80, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[50], + border: Border.all(color: Colors.grey[300]!), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(8), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.radio_button_checked, + size: 16, color: Colors.blue[600]), + const SizedBox(width: 6), + Text( + 'Morse Code:', + style: TextStyle( + fontSize: 12, + color: Colors.grey[700], + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox(height: 4), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Text( + currentMorse.isEmpty ? 'Tap below to input...' : currentMorse, + style: TextStyle( + fontSize: 16, + fontFamily: 'monospace', + fontWeight: FontWeight.w600, + color: currentMorse.isEmpty + ? Colors.grey[500] + : Colors.black87, + ), + ), + if (letterPreview.isNotEmpty) ...[ + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Colors.blue[50], + border: Border.all(color: Colors.blue[200]!), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + letterPreview, + style: TextStyle( + fontSize: 12, + color: Colors.blue[700], + fontFamily: 'monospace', + ), + ), + ), + ], + ], + ), + ), + ), + ], + ), + ), + ], + + // Text output field + TextField( + controller: _internalController, + readOnly: true, + maxLines: 3, + decoration: widget.decoration ?? + InputDecoration( + hintText: widget.autoConvertToText + ? 'Converted text will appear here...' + : 'Morse code will appear here...', + border: OutlineInputBorder( + borderRadius: BorderRadius.vertical( + top: widget.showMorsePreview + ? Radius.zero + : const Radius.circular(8), + bottom: Radius.zero, + ), + ), + suffixIcon: currentMorse.isNotEmpty + ? IconButton( + onPressed: _clearInput, + icon: const Icon(Icons.clear), + tooltip: 'Clear input', + ) + : null, + ), + ), + + // Tap detection area + AnimatedBuilder( + animation: Listenable.merge([ + _dotController, + _dashController, + _spaceController, + ]), + builder: (context, child) { + return GestureDetector( + onTap: _onSingleTap, + onDoubleTap: _onDoubleTap, + onLongPress: _onLongPress, + child: Container( + height: widget.tapAreaHeight, + decoration: BoxDecoration( + color: _getCurrentFeedbackColor().withValues( + alpha: _getCurrentFeedbackValue(), + ), + border: Border.all(color: Colors.grey[300]!), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(8), + ), + ), + child: Stack( + children: [ + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.touch_app, + size: 32, + color: Colors.grey[600], + ), + const SizedBox(height: 8), + Text( + 'Tap for Morse Input', + style: TextStyle( + fontSize: 16, + color: Colors.grey[700], + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + Text( + '1 tap = • | 2 taps = — | Hold = space', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ), + + // Current letter being typed + if (_currentLetter.isNotEmpty) + Positioned( + top: 8, + right: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.white24), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.edit, + color: Colors.yellow[300], + size: 12, + ), + const SizedBox(width: 4), + Text( + _currentLetter.join(''), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + + // Gesture feedback indicators + if (_dotController.isAnimating) + const Positioned( + bottom: 8, + left: 8, + child: Icon(Icons.circle, color: Colors.green, size: 12), + ), + if (_dashController.isAnimating) + const Positioned( + bottom: 8, + left: 28, + child: Icon(Icons.remove, color: Colors.orange, size: 16), + ), + if (_spaceController.isAnimating) + const Positioned( + bottom: 8, + left: 52, + child: Icon(Icons.space_bar, color: Colors.purple, size: 16), + ), + ], + ), + ), + ); + }, + ), + ], + ); + } +} \ No newline at end of file diff --git a/packages/morse_tap/pubspec.yaml b/packages/morse_tap/pubspec.yaml new file mode 100644 index 0000000..da7cd8a --- /dev/null +++ b/packages/morse_tap/pubspec.yaml @@ -0,0 +1,54 @@ +name: morse_tap +description: "A Melos-managed project for mono-repo, created using NonStop CLI." +version: 0.0.1 +homepage: + +environment: + sdk: ^3.8.1 + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/packages/morse_tap/test/morse_tap_test.dart b/packages/morse_tap/test/morse_tap_test.dart new file mode 100644 index 0000000..240cc73 --- /dev/null +++ b/packages/morse_tap/test/morse_tap_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:morse_tap/morse_tap.dart'; + +void main() { + group('MorseCodec', () { + test('converts text to Morse code', () { + expect('SOS'.toMorseCode(), '... --- ...'); + expect('HELLO'.toMorseCode(), '.... . .-.. .-.. ---'); + expect('TEST'.toMorseCode(), '- . ... -'); + }); + + test('converts Morse code to text', () { + expect('... --- ...'.fromMorseCode(), 'SOS'); + expect('.... . .-.. .-.. ---'.fromMorseCode(), 'HELLO'); + expect('- . ... -'.fromMorseCode(), 'TEST'); + }); + + test('validates Morse sequences', () { + expect('... --- ...'.isValidMorseSequence(), true); + expect('... xyz ...'.isValidMorseSequence(), false); + expect(''.isValidMorseSequence(), true); + }); + + test('validates Morse input characters', () { + expect('... --- ...'.isValidMorseInput(), true); + expect('... abc ...'.isValidMorseInput(), false); + expect('.-.. --- ...- .'.isValidMorseInput(), true); + }); + }); + + group('MorseCodec direct methods', () { + test('textToMorse handles multiple words', () { + expect(MorseCodec.textToMorse('HELLO WORLD'), + '.... . .-.. .-.. --- / .-- --- .-. .-.. -..'); + }); + + test('morseToText handles multiple words', () { + expect(MorseCodec.morseToText('... --- ... / - . ... -'), + 'SOS TEST'); + }); + + test('getCharacterDuration returns correct durations', () { + expect(MorseCodec.getCharacterDuration('.'), + const Duration(milliseconds: 100)); + expect(MorseCodec.getCharacterDuration('-'), + const Duration(milliseconds: 300)); + expect(MorseCodec.getCharacterDuration('x'), + const Duration(milliseconds: 0)); + }); + + test('splitMorseSequence extracts dots and dashes', () { + expect(MorseCodec.splitMorseSequence('... --- ...'), + ['.','.','.','-','-','-','.','.','.']); + }); + }); +} From 0346792b34efdf9136df440a19b45d54626f60bb Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 18:05:49 +0530 Subject: [PATCH 02/18] refactor(morse_tap): remove visual feedback from MorseTapDetector - Remove animation controllers and visual overlays - Simplify widget to pure gesture detection - Clean up unused visual feedback parameters - Update tests to reflect removed visual features --- .../lib/src/widgets/morse_tap_detector.dart | 254 +++--------------- packages/morse_tap/test/morse_tap_test.dart | 40 ++- 2 files changed, 66 insertions(+), 228 deletions(-) diff --git a/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart b/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart index 7bdd450..6c635c8 100644 --- a/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart +++ b/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart @@ -6,30 +6,30 @@ import 'package:flutter/material.dart'; /// /// The widget uses discrete gestures: /// - Single tap (onTap) = dot (.) -/// - Double tap (onDoubleTap) = dash (-) +/// - Double tap (onDoubleTap) = dash (-) /// - Long press (onLongPress) = space between letters -/// -/// It validates the tapped sequence against the expected Morse code pattern +/// +/// It validates the tapped sequence against the expected Morse code pattern /// and only triggers [onCorrectSequence] when the correct sequence is completed. +/// +/// The timeout resets after each input, giving users time to enter the next +/// character. This allows for entering long sequences at a comfortable pace. class MorseTapDetector extends StatefulWidget { /// Creates a Morse tap detector widget. /// /// [expectedMorseCode] The Morse code sequence that should be tapped /// [onCorrectSequence] Callback triggered when correct sequence is completed /// [child] The child widget to wrap - /// [sequenceTimeout] Timeout for incomplete sequences - /// [showVisualFeedback] Whether to show visual feedback during input - /// [feedbackColor] Color for visual feedback + /// [inputTimeout] Timeout duration to wait for the next input character const MorseTapDetector({ super.key, required this.expectedMorseCode, required this.onCorrectSequence, required this.child, - this.sequenceTimeout = const Duration(seconds: 10), - this.showVisualFeedback = true, - this.feedbackColor = Colors.blue, + this.inputTimeout = const Duration(seconds: 10), this.onIncorrectSequence, - this.onSequenceTimeout, + this.onInputTimeout, + this.onSequenceChange, this.onDotAdded, this.onDashAdded, this.onSpaceAdded, @@ -44,20 +44,16 @@ class MorseTapDetector extends StatefulWidget { /// The child widget to detect taps on final Widget child; - /// Timeout duration for incomplete sequences - final Duration sequenceTimeout; - - /// Whether to show visual feedback during tap input - final bool showVisualFeedback; - - /// Color for visual feedback overlay - final Color feedbackColor; + /// Timeout duration to wait for the next input character. + /// This resets after each valid input, allowing users to take their time + /// with long sequences as long as they keep entering characters. + final Duration inputTimeout; /// Callback for when an incorrect sequence is detected final VoidCallback? onIncorrectSequence; - /// Callback for when a sequence times out - final VoidCallback? onSequenceTimeout; + /// Callback for when input times out (no input received within timeout duration) + final VoidCallback? onInputTimeout; /// Callback when a dot is added final VoidCallback? onDotAdded; @@ -68,79 +64,52 @@ class MorseTapDetector extends StatefulWidget { /// Callback when a space is added final VoidCallback? onSpaceAdded; + /// Callback when the sequence changes + /// Provides the current sequence string, empty when incorrect or reset + final ValueChanged? onSequenceChange; + @override State createState() => _MorseTapDetectorState(); } -class _MorseTapDetectorState extends State - with TickerProviderStateMixin { +class _MorseTapDetectorState extends State { final List _currentSequence = []; Timer? _timeoutTimer; - - late AnimationController _feedbackController; - - late AnimationController _dotController; - late AnimationController _dashController; - late AnimationController _spaceController; @override void initState() { super.initState(); - - // Feedback animation controller (unused but kept for potential future use) - _feedbackController = AnimationController( - duration: const Duration(milliseconds: 100), - vsync: this, - ); - - // Individual gesture feedback animations - _dotController = AnimationController( - duration: const Duration(milliseconds: 200), - vsync: this, - ); - - _dashController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - - _spaceController = AnimationController( - duration: const Duration(milliseconds: 150), - vsync: this, - ); } @override void dispose() { _timeoutTimer?.cancel(); - _feedbackController.dispose(); - _dotController.dispose(); - _dashController.dispose(); - _spaceController.dispose(); super.dispose(); } - void _startSequenceTimeout() { + void _startInputTimeout() { _timeoutTimer?.cancel(); - _timeoutTimer = Timer(widget.sequenceTimeout, () { + _timeoutTimer = Timer(widget.inputTimeout, () { _resetSequence(); - widget.onSequenceTimeout?.call(); + widget.onInputTimeout?.call(); }); } void _resetSequence() { _currentSequence.clear(); _timeoutTimer?.cancel(); - if (mounted) { - setState(() {}); - } + // Notify sequence change with empty string on reset + widget.onSequenceChange?.call(''); } void _addMorseCharacter(String character) { _currentSequence.add(character); - _startSequenceTimeout(); - setState(() {}); - + _startInputTimeout(); + + // Notify sequence change + final currentMorse = _currentSequence.join(''); + widget.onSequenceChange?.call(currentMorse); + // Check if sequence matches expected pattern _checkSequence(); } @@ -149,33 +118,18 @@ class _MorseTapDetectorState extends State // Single tap = dot _addMorseCharacter('.'); widget.onDotAdded?.call(); - - // Visual feedback - if (widget.showVisualFeedback) { - _dotController.forward().then((_) => _dotController.reverse()); - } } void _onDoubleTap() { // Double tap = dash _addMorseCharacter('-'); widget.onDashAdded?.call(); - - // Visual feedback - if (widget.showVisualFeedback) { - _dashController.forward().then((_) => _dashController.reverse()); - } } void _onLongPress() { // Long press = space (letter separator) _addMorseCharacter(' '); widget.onSpaceAdded?.call(); - - // Visual feedback - if (widget.showVisualFeedback) { - _spaceController.forward().then((_) => _spaceController.reverse()); - } } void _checkSequence() { @@ -184,156 +138,24 @@ class _MorseTapDetectorState extends State if (currentMorse == expectedMorse) { // Correct sequence detected! - _resetSequence(); widget.onCorrectSequence(); - } else if (currentMorse.length >= expectedMorse.length || - !expectedMorse.startsWith(currentMorse)) { - // Sequence is wrong or too long _resetSequence(); + } else if (currentMorse.length >= expectedMorse.length || + !expectedMorse.startsWith(currentMorse)) { + // Sequence is wrong or too long widget.onIncorrectSequence?.call(); + _resetSequence(); } // Otherwise, continue waiting for more input } - String get _currentMorseDisplay { - return _currentSequence.join(''); - } - - Color _getCurrentFeedbackColor() { - if (_dotController.isAnimating) { - return Colors.green; - } else if (_dashController.isAnimating) { - return Colors.orange; - } else if (_spaceController.isAnimating) { - return Colors.purple; - } - return widget.feedbackColor; - } - - double _getCurrentFeedbackValue() { - if (_dotController.isAnimating) { - return _dotController.value * 0.3; - } else if (_dashController.isAnimating) { - return _dashController.value * 0.3; - } else if (_spaceController.isAnimating) { - return _spaceController.value * 0.3; - } - return 0.0; - } - @override Widget build(BuildContext context) { return GestureDetector( onTap: _onSingleTap, onDoubleTap: _onDoubleTap, onLongPress: _onLongPress, - child: AnimatedBuilder( - animation: Listenable.merge([ - _dotController, - _dashController, - _spaceController, - ]), - builder: (context, child) { - return Stack( - children: [ - // Base widget with feedback overlay - Container( - decoration: BoxDecoration( - color: widget.showVisualFeedback - ? _getCurrentFeedbackColor().withValues( - alpha: _getCurrentFeedbackValue(), - ) - : null, - borderRadius: BorderRadius.circular(8), - ), - child: widget.child, - ), - - // Current sequence display - if (_currentSequence.isNotEmpty) - Positioned( - top: 8, - right: 8, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: Colors.black87, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white24), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.radio_button_checked, - color: Colors.blue[300], - size: 12, - ), - const SizedBox(width: 6), - Text( - _currentMorseDisplay, - style: const TextStyle( - color: Colors.white, - fontSize: 14, - fontFamily: 'monospace', - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), - - // Gesture hints overlay - if (_currentSequence.isEmpty) - Positioned( - bottom: 8, - left: 8, - right: 8, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(12), - ), - child: const Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - '1 tap = •', - style: TextStyle( - color: Colors.white70, - fontSize: 10, - ), - ), - Text( - '2 taps = —', - style: TextStyle( - color: Colors.white70, - fontSize: 10, - ), - ), - Text( - 'Hold = space', - style: TextStyle( - color: Colors.white70, - fontSize: 10, - ), - ), - ], - ), - ), - ), - ], - ); - }, - ), + child: widget.child, ); } -} \ No newline at end of file +} diff --git a/packages/morse_tap/test/morse_tap_test.dart b/packages/morse_tap/test/morse_tap_test.dart index 240cc73..7c496ba 100644 --- a/packages/morse_tap/test/morse_tap_test.dart +++ b/packages/morse_tap/test/morse_tap_test.dart @@ -31,27 +31,43 @@ void main() { group('MorseCodec direct methods', () { test('textToMorse handles multiple words', () { - expect(MorseCodec.textToMorse('HELLO WORLD'), - '.... . .-.. .-.. --- / .-- --- .-. .-.. -..'); + expect( + MorseCodec.textToMorse('HELLO WORLD'), + '.... . .-.. .-.. --- / .-- --- .-. .-.. -..', + ); }); test('morseToText handles multiple words', () { - expect(MorseCodec.morseToText('... --- ... / - . ... -'), - 'SOS TEST'); + expect(MorseCodec.morseToText('... --- ... / - . ... -'), 'SOS TEST'); }); test('getCharacterDuration returns correct durations', () { - expect(MorseCodec.getCharacterDuration('.'), - const Duration(milliseconds: 100)); - expect(MorseCodec.getCharacterDuration('-'), - const Duration(milliseconds: 300)); - expect(MorseCodec.getCharacterDuration('x'), - const Duration(milliseconds: 0)); + expect( + MorseCodec.getCharacterDuration('.'), + const Duration(milliseconds: 100), + ); + expect( + MorseCodec.getCharacterDuration('-'), + const Duration(milliseconds: 300), + ); + expect( + MorseCodec.getCharacterDuration('x'), + const Duration(milliseconds: 0), + ); }); test('splitMorseSequence extracts dots and dashes', () { - expect(MorseCodec.splitMorseSequence('... --- ...'), - ['.','.','.','-','-','-','.','.','.']); + expect(MorseCodec.splitMorseSequence('... --- ...'), [ + '.', + '.', + '.', + '-', + '-', + '-', + '.', + '.', + '.', + ]); }); }); } From 2034f6103637c487a8723850b2300da938053d7a Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 18:06:32 +0530 Subject: [PATCH 03/18] feat(examples): integrate onSequenceChange callback with real-time UI - Add current sequence display with color-coded feedback - Use onSequenceChange for real-time progress updates - Update parameter names to match new API - Clear sequence when switching targets --- packages/morse_tap/example/lib/main.dart | 33 +++++++++++++++++-- packages/morse_tap/example/main.dart | 40 ++++++++++++++++++++---- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/packages/morse_tap/example/lib/main.dart b/packages/morse_tap/example/lib/main.dart index 371ca66..f5daf40 100644 --- a/packages/morse_tap/example/lib/main.dart +++ b/packages/morse_tap/example/lib/main.dart @@ -104,6 +104,7 @@ class _MorseTapDetectorExampleState extends State { String _currentTarget = 'SOS'; Color _buttonColor = Colors.blue; String _gestureHint = ''; + String _currentSequence = ''; final Map _targets = { 'SOS': '... --- ...', @@ -192,6 +193,13 @@ class _MorseTapDetectorExampleState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + MorseTapDetector( + expectedMorseCode: '...', + onCorrectSequence: () {}, + child: CircleAvatar(), + ), + const SizedBox(height: 16), + Card( child: Padding( padding: const EdgeInsets.all(16.0), @@ -221,6 +229,22 @@ class _MorseTapDetectorExampleState extends State { fontFamily: 'monospace', ), ), + if (_currentSequence.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + 'Current: $_currentSequence', + style: TextStyle( + fontSize: 14, + fontFamily: 'monospace', + color: + _targets[_currentTarget]!.startsWith( + _currentSequence, + ) + ? Colors.green + : Colors.orange, + ), + ), + ], ], ), ), @@ -247,6 +271,7 @@ class _MorseTapDetectorExampleState extends State { 'Use gestures to input $target (${_targets[target]})'; _buttonColor = Colors.blue; _gestureHint = ''; + _currentSequence = ''; }); } }, @@ -264,11 +289,15 @@ class _MorseTapDetectorExampleState extends State { expectedMorseCode: _targets[_currentTarget]!, onCorrectSequence: _onCorrectSequence, onIncorrectSequence: _onIncorrectSequence, - onSequenceTimeout: _onTimeout, + onInputTimeout: _onTimeout, + onSequenceChange: (sequence) { + setState(() { + _currentSequence = sequence; + }); + }, onDotAdded: _onDotAdded, onDashAdded: _onDashAdded, onSpaceAdded: _onSpaceAdded, - feedbackColor: _buttonColor, child: AnimatedContainer( duration: const Duration(milliseconds: 300), decoration: BoxDecoration( diff --git a/packages/morse_tap/example/main.dart b/packages/morse_tap/example/main.dart index 07153fb..6b1fc49 100644 --- a/packages/morse_tap/example/main.dart +++ b/packages/morse_tap/example/main.dart @@ -104,6 +104,7 @@ class _MorseTapDetectorExampleState extends State { String _currentTarget = 'SOS'; Color _buttonColor = Colors.blue; String _gestureHint = ''; + String _currentSequence = ''; final Map _targets = { 'SOS': '... --- ...', @@ -123,7 +124,8 @@ class _MorseTapDetectorExampleState extends State { Future.delayed(const Duration(seconds: 2), () { if (mounted) { setState(() { - _message = 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _message = + 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; _buttonColor = Colors.blue; }); } @@ -140,7 +142,8 @@ class _MorseTapDetectorExampleState extends State { Future.delayed(const Duration(seconds: 1), () { if (mounted) { setState(() { - _message = 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _message = + 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; _buttonColor = Colors.blue; }); } @@ -157,7 +160,8 @@ class _MorseTapDetectorExampleState extends State { Future.delayed(const Duration(seconds: 1), () { if (mounted) { setState(() { - _message = 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; + _message = + 'Use gestures to input $_currentTarget (${_targets[_currentTarget]})'; _buttonColor = Colors.blue; }); } @@ -218,6 +222,22 @@ class _MorseTapDetectorExampleState extends State { fontFamily: 'monospace', ), ), + if (_currentSequence.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + 'Current: $_currentSequence', + style: TextStyle( + fontSize: 14, + fontFamily: 'monospace', + color: + _targets[_currentTarget]!.startsWith( + _currentSequence, + ) + ? Colors.green + : Colors.orange, + ), + ), + ], ], ), ), @@ -244,6 +264,7 @@ class _MorseTapDetectorExampleState extends State { 'Use gestures to input $target (${_targets[target]})'; _buttonColor = Colors.blue; _gestureHint = ''; + _currentSequence = ''; }); } }, @@ -261,11 +282,15 @@ class _MorseTapDetectorExampleState extends State { expectedMorseCode: _targets[_currentTarget]!, onCorrectSequence: _onCorrectSequence, onIncorrectSequence: _onIncorrectSequence, - onSequenceTimeout: _onTimeout, + onInputTimeout: _onTimeout, + onSequenceChange: (sequence) { + setState(() { + _currentSequence = sequence; + }); + }, onDotAdded: _onDotAdded, onDashAdded: _onDashAdded, onSpaceAdded: _onSpaceAdded, - feedbackColor: _buttonColor, child: AnimatedContainer( duration: const Duration(milliseconds: 300), decoration: BoxDecoration( @@ -324,7 +349,10 @@ class _MorseTapDetectorExampleState extends State { if (_gestureHint.isNotEmpty) ...[ const SizedBox(height: 8), Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.blue[50], border: Border.all(color: Colors.blue[200]!), From 25483b5baa9aa611f85290a7f654bbe31b588505 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 18:06:48 +0530 Subject: [PATCH 04/18] docs(morse_tap): add screenshot and improve documentation - Add morse_tap.png screenshot to package - Include screenshot in README and pubspec.yaml - Fix incorrect callback names in README examples - Update package description and repository metadata - Document onSequenceChange callback usage --- packages/morse_tap/README.md | 24 +++++++++++++++--------- packages/morse_tap/morse_tap.png | Bin 0 -> 292864 bytes packages/morse_tap/pubspec.yaml | 9 +++++++-- 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 packages/morse_tap/morse_tap.png diff --git a/packages/morse_tap/README.md b/packages/morse_tap/README.md index f246d0b..de15ca3 100644 --- a/packages/morse_tap/README.md +++ b/packages/morse_tap/README.md @@ -17,6 +17,8 @@ A Flutter package that provides Morse code input functionality using intuitive gestures. Create interactive Morse code experiences with single taps for dots, double taps for dashes, and long presses for spaces. +![Morse Tap Demo](morse_tap.png) + ## Features ✨ **MorseTapDetector** - Widget that detects specific Morse code patterns using gestures @@ -69,6 +71,10 @@ MorseTapDetector( onIncorrectSequence: () { print("Wrong pattern, try again"); }, + onSequenceChange: (sequence) { + print("Current sequence: $sequence"); + // Update UI with current input + }, onDotAdded: () => print("Dot added"), onDashAdded: () => print("Dash added"), onSpaceAdded: () => print("Space added"), @@ -153,20 +159,20 @@ print(isMorseInput); // false ### Timing Configuration -Customize tap timing thresholds: +Customize the input timeout: ```dart MorseTapDetector( - expectedMorseCode: "...", - dotThreshold: Duration(milliseconds: 150), // Shorter for dots - dashThreshold: Duration(milliseconds: 400), // Longer for dashes - letterGap: Duration(milliseconds: 600), // Gap between letters - sequenceTimeout: Duration(seconds: 5), // Reset timeout - onTap: () => print("Correct!"), + expectedMorseCode: "... --- ...", + inputTimeout: Duration(seconds: 5), // Time allowed for next input + onCorrectSequence: () => print("Correct!"), child: MyButton(), ) ``` +Note: The timeout resets after each input, allowing users to take their time +with long sequences as long as they keep entering characters. + ### Visual Feedback Control visual feedback options: @@ -214,7 +220,7 @@ final customPattern = "HELP".toMorseCode(); MorseTapDetector( expectedMorseCode: customPattern, - onTap: () => showDialog( + onCorrectSequence: () => showDialog( context: context, builder: (context) => AlertDialog( title: Text("Help Requested!"), @@ -243,7 +249,7 @@ class MultiPatternDetector extends StatelessWidget { children: patterns.entries.map((entry) { return MorseTapDetector( expectedMorseCode: entry.value, - onTap: () => handlePattern(entry.key), + onCorrectSequence: () => handlePattern(entry.key), child: PatternButton(label: entry.key), ); }).toList(), diff --git a/packages/morse_tap/morse_tap.png b/packages/morse_tap/morse_tap.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc1278f8670054048c515574e55ddafc7f520ac GIT binary patch literal 292864 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%0Do%m6_%7XnCsHAKvtZna{n5x^s zAlYo};K(i6aIEy~U@2aAGbQH~m;u~(u7;k#dmqovgB3Z@W*VJgZT%KQfWp*N`EsKQ z0=kxI>z!cMr4;a~Cf&-%kAEE+M!*1}JS$Yha0Qn4F6kC->$qV}km`ma1EMD5m9bB^QClu232DrAp z;$p7+EN&f0&n+yI8?_G5rvoX=Zr<-4xmjeHQ8&+=qs;;@65nfCF;3n|B)% Date: Fri, 22 Aug 2025 18:07:07 +0530 Subject: [PATCH 05/18] refactor(morse_tap): clean up morse_text_input formatting - Apply code formatting and minor cleanup - Maintain consistent code style across package --- .../lib/src/widgets/morse_text_input.dart | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/packages/morse_tap/lib/src/widgets/morse_text_input.dart b/packages/morse_tap/lib/src/widgets/morse_text_input.dart index 63b60de..7fedd07 100644 --- a/packages/morse_tap/lib/src/widgets/morse_text_input.dart +++ b/packages/morse_tap/lib/src/widgets/morse_text_input.dart @@ -9,7 +9,7 @@ import '../morse_algorithm.dart'; /// - Single tap = dot (.) /// - Double tap = dash (-) /// - Long press = space between letters -/// +/// /// It converts Morse patterns into readable text automatically or provides raw Morse code. class MorseTextInput extends StatefulWidget { /// Creates a Morse text input widget. @@ -132,7 +132,7 @@ class _MorseTextInputState extends State // Cancel pending timers and start letter gap timer _letterGapTimer?.cancel(); _wordGapTimer?.cancel(); - + _letterGapTimer = Timer(widget.letterGap, () { _completeLetter(); }); @@ -148,7 +148,7 @@ class _MorseTextInputState extends State // Cancel pending timers and start letter gap timer _letterGapTimer?.cancel(); _wordGapTimer?.cancel(); - + _letterGapTimer = Timer(widget.letterGap, () { _completeLetter(); }); @@ -159,14 +159,14 @@ class _MorseTextInputState extends State void _onLongPress() { // Long press = complete current letter and add space _spaceController.forward().then((_) => _spaceController.reverse()); - + _letterGapTimer?.cancel(); _wordGapTimer?.cancel(); - + if (_currentLetter.isNotEmpty) { _completeLetter(); } - + // Force word completion after a short delay to allow letter to process Timer(const Duration(milliseconds: 100), () { _completeWord(); @@ -310,8 +310,11 @@ class _MorseTextInputState extends State children: [ Row( children: [ - Icon(Icons.radio_button_checked, - size: 16, color: Colors.blue[600]), + Icon( + Icons.radio_button_checked, + size: 16, + color: Colors.blue[600], + ), const SizedBox(width: 6), Text( 'Morse Code:', @@ -330,13 +333,15 @@ class _MorseTextInputState extends State child: Row( children: [ Text( - currentMorse.isEmpty ? 'Tap below to input...' : currentMorse, + currentMorse.isEmpty + ? 'Tap below to input...' + : currentMorse, style: TextStyle( fontSize: 16, fontFamily: 'monospace', fontWeight: FontWeight.w600, - color: currentMorse.isEmpty - ? Colors.grey[500] + color: currentMorse.isEmpty + ? Colors.grey[500] : Colors.black87, ), ), @@ -344,7 +349,9 @@ class _MorseTextInputState extends State const SizedBox(width: 12), Container( padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), + horizontal: 8, + vertical: 2, + ), decoration: BoxDecoration( color: Colors.blue[50], border: Border.all(color: Colors.blue[200]!), @@ -374,7 +381,8 @@ class _MorseTextInputState extends State controller: _internalController, readOnly: true, maxLines: 3, - decoration: widget.decoration ?? + decoration: + widget.decoration ?? InputDecoration( hintText: widget.autoConvertToText ? 'Converted text will appear here...' @@ -451,7 +459,7 @@ class _MorseTextInputState extends State ], ), ), - + // Current letter being typed if (_currentLetter.isNotEmpty) Positioned( @@ -495,19 +503,31 @@ class _MorseTextInputState extends State const Positioned( bottom: 8, left: 8, - child: Icon(Icons.circle, color: Colors.green, size: 12), + child: Icon( + Icons.circle, + color: Colors.green, + size: 12, + ), ), if (_dashController.isAnimating) const Positioned( bottom: 8, left: 28, - child: Icon(Icons.remove, color: Colors.orange, size: 16), + child: Icon( + Icons.remove, + color: Colors.orange, + size: 16, + ), ), if (_spaceController.isAnimating) const Positioned( bottom: 8, left: 52, - child: Icon(Icons.space_bar, color: Colors.purple, size: 16), + child: Icon( + Icons.space_bar, + color: Colors.purple, + size: 16, + ), ), ], ), @@ -518,4 +538,4 @@ class _MorseTextInputState extends State ], ); } -} \ No newline at end of file +} From 2119d0a5adff9427f8504169f92f7c3b9acea411 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 18:57:12 +0530 Subject: [PATCH 06/18] feat(morse_tap): add haptic feedback system - Add HapticConfig model with preset configurations - Add HapticFeedbackType enum for Flutter haptic mapping - Add HapticUtils with platform detection and safe execution - Export haptic components from main library --- packages/morse_tap/lib/morse_tap.dart | 3 + .../lib/src/models/haptic_config.dart | 136 ++++++++++++++++ .../lib/src/models/haptic_feedback_type.dart | 18 +++ .../morse_tap/lib/src/utils/haptic_utils.dart | 148 ++++++++++++++++++ 4 files changed, 305 insertions(+) create mode 100644 packages/morse_tap/lib/src/models/haptic_config.dart create mode 100644 packages/morse_tap/lib/src/models/haptic_feedback_type.dart create mode 100644 packages/morse_tap/lib/src/utils/haptic_utils.dart diff --git a/packages/morse_tap/lib/morse_tap.dart b/packages/morse_tap/lib/morse_tap.dart index 8d4e3e0..930d48b 100644 --- a/packages/morse_tap/lib/morse_tap.dart +++ b/packages/morse_tap/lib/morse_tap.dart @@ -4,7 +4,10 @@ /// tap input to text, and utility extensions for working with Morse code. library; +export 'src/models/haptic_config.dart'; +export 'src/models/haptic_feedback_type.dart'; export 'src/morse_algorithm.dart'; export 'src/morse_extensions.dart'; +export 'src/utils/haptic_utils.dart'; export 'src/widgets/morse_tap_detector.dart'; export 'src/widgets/morse_text_input.dart'; diff --git a/packages/morse_tap/lib/src/models/haptic_config.dart b/packages/morse_tap/lib/src/models/haptic_config.dart new file mode 100644 index 0000000..6564ef7 --- /dev/null +++ b/packages/morse_tap/lib/src/models/haptic_config.dart @@ -0,0 +1,136 @@ +import 'haptic_feedback_type.dart'; + +/// Configuration class for haptic feedback settings in Morse code widgets. +/// +/// This class defines the haptic feedback intensity for different Morse code +/// gestures and events, allowing users to customize their tactile experience. +class HapticConfig { + /// Creates a haptic configuration. + const HapticConfig({ + this.enabled = false, + this.dotIntensity = HapticFeedbackType.lightImpact, + this.dashIntensity = HapticFeedbackType.mediumImpact, + this.spaceIntensity = HapticFeedbackType.heavyImpact, + this.correctSequenceIntensity = HapticFeedbackType.mediumImpact, + this.incorrectSequenceIntensity = HapticFeedbackType.heavyImpact, + this.timeoutIntensity = HapticFeedbackType.lightImpact, + }); + + /// Whether haptic feedback is enabled globally + final bool enabled; + + /// Haptic intensity for dots (single taps) + final HapticFeedbackType dotIntensity; + + /// Haptic intensity for dashes (double taps) + final HapticFeedbackType dashIntensity; + + /// Haptic intensity for spaces (long press) + final HapticFeedbackType spaceIntensity; + + /// Haptic intensity for correct sequence completion + final HapticFeedbackType correctSequenceIntensity; + + /// Haptic intensity for incorrect sequences + final HapticFeedbackType incorrectSequenceIntensity; + + /// Haptic intensity for input timeout + final HapticFeedbackType timeoutIntensity; + + /// Creates a copy of this config with the given fields replaced with new values. + HapticConfig copyWith({ + bool? enabled, + HapticFeedbackType? dotIntensity, + HapticFeedbackType? dashIntensity, + HapticFeedbackType? spaceIntensity, + HapticFeedbackType? correctSequenceIntensity, + HapticFeedbackType? incorrectSequenceIntensity, + HapticFeedbackType? timeoutIntensity, + }) { + return HapticConfig( + enabled: enabled ?? this.enabled, + dotIntensity: dotIntensity ?? this.dotIntensity, + dashIntensity: dashIntensity ?? this.dashIntensity, + spaceIntensity: spaceIntensity ?? this.spaceIntensity, + correctSequenceIntensity: + correctSequenceIntensity ?? this.correctSequenceIntensity, + incorrectSequenceIntensity: + incorrectSequenceIntensity ?? this.incorrectSequenceIntensity, + timeoutIntensity: timeoutIntensity ?? this.timeoutIntensity, + ); + } + + /// Default haptic configuration with moderate settings + static const HapticConfig defaultConfig = HapticConfig( + enabled: true, + dotIntensity: HapticFeedbackType.lightImpact, + dashIntensity: HapticFeedbackType.lightImpact, + spaceIntensity: HapticFeedbackType.lightImpact, + correctSequenceIntensity: HapticFeedbackType.mediumImpact, + incorrectSequenceIntensity: HapticFeedbackType.mediumImpact, + timeoutIntensity: HapticFeedbackType.mediumImpact, + ); + + /// Disabled haptic configuration + static const HapticConfig disabled = HapticConfig(enabled: false); + + /// Light haptic configuration with subtle feedback + static const HapticConfig light = HapticConfig( + enabled: true, + dotIntensity: HapticFeedbackType.selectionClick, + dashIntensity: HapticFeedbackType.lightImpact, + spaceIntensity: HapticFeedbackType.mediumImpact, + correctSequenceIntensity: HapticFeedbackType.lightImpact, + incorrectSequenceIntensity: HapticFeedbackType.mediumImpact, + timeoutIntensity: HapticFeedbackType.selectionClick, + ); + + /// Strong haptic configuration with intense feedback + static const HapticConfig strong = HapticConfig( + enabled: true, + dotIntensity: HapticFeedbackType.mediumImpact, + dashIntensity: HapticFeedbackType.heavyImpact, + spaceIntensity: HapticFeedbackType.heavyImpact, + correctSequenceIntensity: HapticFeedbackType.heavyImpact, + incorrectSequenceIntensity: HapticFeedbackType.heavyImpact, + timeoutIntensity: HapticFeedbackType.mediumImpact, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is HapticConfig && + other.enabled == enabled && + other.dotIntensity == dotIntensity && + other.dashIntensity == dashIntensity && + other.spaceIntensity == spaceIntensity && + other.correctSequenceIntensity == correctSequenceIntensity && + other.incorrectSequenceIntensity == incorrectSequenceIntensity && + other.timeoutIntensity == timeoutIntensity; + } + + @override + int get hashCode { + return Object.hash( + enabled, + dotIntensity, + dashIntensity, + spaceIntensity, + correctSequenceIntensity, + incorrectSequenceIntensity, + timeoutIntensity, + ); + } + + @override + String toString() { + return 'HapticConfig(' + 'enabled: $enabled, ' + 'dotIntensity: $dotIntensity, ' + 'dashIntensity: $dashIntensity, ' + 'spaceIntensity: $spaceIntensity, ' + 'correctSequenceIntensity: $correctSequenceIntensity, ' + 'incorrectSequenceIntensity: $incorrectSequenceIntensity, ' + 'timeoutIntensity: $timeoutIntensity)'; + } +} diff --git a/packages/morse_tap/lib/src/models/haptic_feedback_type.dart b/packages/morse_tap/lib/src/models/haptic_feedback_type.dart new file mode 100644 index 0000000..036ebe0 --- /dev/null +++ b/packages/morse_tap/lib/src/models/haptic_feedback_type.dart @@ -0,0 +1,18 @@ +/// Custom enum for haptic feedback types to provide a unified interface +/// for different haptic feedback intensities available in Flutter. +enum HapticFeedbackType { + /// Light haptic feedback - subtle, gentle touch sensation + lightImpact, + + /// Medium haptic feedback - moderate, noticeable touch sensation + mediumImpact, + + /// Heavy haptic feedback - strong, pronounced touch sensation + heavyImpact, + + /// Selection click feedback - quick, precise feedback for selections + selectionClick, + + /// Vibration feedback - standard system vibration pattern + vibrate, +} diff --git a/packages/morse_tap/lib/src/utils/haptic_utils.dart b/packages/morse_tap/lib/src/utils/haptic_utils.dart new file mode 100644 index 0000000..ceb2890 --- /dev/null +++ b/packages/morse_tap/lib/src/utils/haptic_utils.dart @@ -0,0 +1,148 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../models/haptic_config.dart'; +import '../models/haptic_feedback_type.dart'; + +/// Utility class for managing haptic feedback in Morse code widgets. +/// +/// Provides safe execution of haptic feedback with platform detection, +/// error handling, and user-friendly descriptions for haptic types. +class HapticUtils { + HapticUtils._(); + + /// Map of haptic feedback types to user-friendly display names + static final Map hapticTypeNames = { + HapticFeedbackType.lightImpact: 'Light', + HapticFeedbackType.mediumImpact: 'Medium', + HapticFeedbackType.heavyImpact: 'Heavy', + HapticFeedbackType.selectionClick: 'Selection', + HapticFeedbackType.vibrate: 'Vibrate', + }; + + /// Map of haptic feedback types to detailed descriptions + static final Map hapticTypeDescriptions = { + HapticFeedbackType.lightImpact: 'Subtle, gentle feedback', + HapticFeedbackType.mediumImpact: 'Moderate, noticeable feedback', + HapticFeedbackType.heavyImpact: 'Strong, pronounced feedback', + HapticFeedbackType.selectionClick: 'Quick selection feedback', + HapticFeedbackType.vibrate: 'Standard vibration pattern', + }; + + /// List of available haptic feedback types in order of intensity + static final List availableHapticTypes = [ + HapticFeedbackType.selectionClick, + HapticFeedbackType.lightImpact, + HapticFeedbackType.mediumImpact, + HapticFeedbackType.heavyImpact, + HapticFeedbackType.vibrate, + ]; + + /// Checks if haptic feedback is supported on the current platform + static bool get isHapticSupported { + if (kIsWeb) return false; + return Platform.isIOS || Platform.isAndroid; + } + + /// Safely executes haptic feedback with error handling + /// + /// Returns true if haptic was executed successfully, false otherwise. + static Future triggerHaptic(HapticFeedbackType? type) async { + if (type == null || !isHapticSupported) { + return false; + } + + try { + switch (type) { + case HapticFeedbackType.lightImpact: + await HapticFeedback.lightImpact(); + case HapticFeedbackType.mediumImpact: + await HapticFeedback.mediumImpact(); + case HapticFeedbackType.heavyImpact: + await HapticFeedback.heavyImpact(); + case HapticFeedbackType.selectionClick: + await HapticFeedback.selectionClick(); + case HapticFeedbackType.vibrate: + await HapticFeedback.vibrate(); + } + return true; + } catch (e) { + // Silently handle haptic feedback errors + debugPrint('Haptic feedback error: $e'); + return false; + } + } + + /// Executes haptic feedback based on configuration + /// + /// Only triggers if haptic is enabled in the config and supported by platform. + static Future executeFromConfig( + HapticConfig? config, + HapticFeedbackType type, + ) async { + if (config == null || !config.enabled) { + return false; + } + return await triggerHaptic(type); + } + + /// Gets the display name for a haptic feedback type + static String getHapticTypeName(HapticFeedbackType type) { + return hapticTypeNames[type] ?? 'Unknown'; + } + + /// Gets the description for a haptic feedback type + static String getHapticTypeDescription(HapticFeedbackType type) { + return hapticTypeDescriptions[type] ?? 'No description available'; + } + + /// Creates a dropdown item for a haptic feedback type + static DropdownMenuItem createDropdownItem( + HapticFeedbackType type, + ) { + return DropdownMenuItem( + value: type, + child: Text(getHapticTypeName(type)), + ); + } + + /// Preset configurations for quick selection + static final Map presetConfigs = { + 'Disabled': HapticConfig.disabled, + 'Light': HapticConfig.light, + 'Default': HapticConfig.defaultConfig, + 'Strong': HapticConfig.strong, + }; + + /// Gets the name of a preset configuration, if it matches + static String? getPresetName(HapticConfig config) { + for (final entry in presetConfigs.entries) { + if (entry.value == config) { + return entry.key; + } + } + return null; + } + + /// Tests haptic feedback by triggering it immediately + /// + /// Used for preview functionality in configuration dialogs. + static Future testHaptic(HapticFeedbackType type) async { + if (isHapticSupported) { + await triggerHaptic(type); + // Add a small delay to prevent rapid-fire haptics + await Future.delayed(const Duration(milliseconds: 100)); + } + } + + /// Validates a haptic configuration + /// + /// Returns null if valid, or an error message if invalid. + static String? validateConfig(HapticConfig config) { + if (!isHapticSupported && config.enabled) { + return 'Haptic feedback is not supported on this platform'; + } + return null; + } +} From b16b4f4099b6fe052b19f257ff4c73985d46febe Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 18:57:29 +0530 Subject: [PATCH 07/18] feat(morse_tap): integrate haptic feedback and fix UI overflow - Add haptic support to MorseTapDetector widget - Fix MorseTextInput overflow by removing fixed height container - Optimize preview layout with flexible sizing --- .../lib/src/widgets/morse_tap_detector.dart | 58 ++++++++++++++ .../lib/src/widgets/morse_text_input.dart | 78 +++++++++---------- 2 files changed, 96 insertions(+), 40 deletions(-) diff --git a/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart b/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart index 6c635c8..e19624d 100644 --- a/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart +++ b/packages/morse_tap/lib/src/widgets/morse_tap_detector.dart @@ -1,5 +1,8 @@ import 'dart:async'; + import 'package:flutter/material.dart'; +import '../models/haptic_config.dart'; +import '../utils/haptic_utils.dart'; /// A widget that detects Morse code tap patterns and triggers callbacks /// when the correct sequence is tapped. @@ -27,6 +30,7 @@ class MorseTapDetector extends StatefulWidget { required this.onCorrectSequence, required this.child, this.inputTimeout = const Duration(seconds: 10), + this.hapticConfig, this.onIncorrectSequence, this.onInputTimeout, this.onSequenceChange, @@ -49,6 +53,10 @@ class MorseTapDetector extends StatefulWidget { /// with long sequences as long as they keep entering characters. final Duration inputTimeout; + /// Haptic feedback configuration + /// If null, no haptic feedback will be provided + final HapticConfig? hapticConfig; + /// Callback for when an incorrect sequence is detected final VoidCallback? onIncorrectSequence; @@ -90,6 +98,14 @@ class _MorseTapDetectorState extends State { void _startInputTimeout() { _timeoutTimer?.cancel(); _timeoutTimer = Timer(widget.inputTimeout, () { + // Trigger timeout haptic feedback + if (widget.hapticConfig != null) { + HapticUtils.executeFromConfig( + widget.hapticConfig, + widget.hapticConfig!.timeoutIntensity, + ); + } + _resetSequence(); widget.onInputTimeout?.call(); }); @@ -118,18 +134,42 @@ class _MorseTapDetectorState extends State { // Single tap = dot _addMorseCharacter('.'); widget.onDotAdded?.call(); + + // Trigger haptic feedback + if (widget.hapticConfig != null) { + HapticUtils.executeFromConfig( + widget.hapticConfig, + widget.hapticConfig!.dotIntensity, + ); + } } void _onDoubleTap() { // Double tap = dash _addMorseCharacter('-'); widget.onDashAdded?.call(); + + // Trigger haptic feedback + if (widget.hapticConfig != null) { + HapticUtils.executeFromConfig( + widget.hapticConfig, + widget.hapticConfig!.dashIntensity, + ); + } } void _onLongPress() { // Long press = space (letter separator) _addMorseCharacter(' '); widget.onSpaceAdded?.call(); + + // Trigger haptic feedback + if (widget.hapticConfig != null) { + HapticUtils.executeFromConfig( + widget.hapticConfig, + widget.hapticConfig!.spaceIntensity, + ); + } } void _checkSequence() { @@ -139,11 +179,29 @@ class _MorseTapDetectorState extends State { if (currentMorse == expectedMorse) { // Correct sequence detected! widget.onCorrectSequence(); + + // Trigger success haptic feedback + if (widget.hapticConfig != null) { + HapticUtils.executeFromConfig( + widget.hapticConfig, + widget.hapticConfig!.correctSequenceIntensity, + ); + } + _resetSequence(); } else if (currentMorse.length >= expectedMorse.length || !expectedMorse.startsWith(currentMorse)) { // Sequence is wrong or too long widget.onIncorrectSequence?.call(); + + // Trigger error haptic feedback + if (widget.hapticConfig != null) { + HapticUtils.executeFromConfig( + widget.hapticConfig, + widget.hapticConfig!.incorrectSequenceIntensity, + ); + } + _resetSequence(); } // Otherwise, continue waiting for more input diff --git a/packages/morse_tap/lib/src/widgets/morse_text_input.dart b/packages/morse_tap/lib/src/widgets/morse_text_input.dart index 7fedd07..63e6054 100644 --- a/packages/morse_tap/lib/src/widgets/morse_text_input.dart +++ b/packages/morse_tap/lib/src/widgets/morse_text_input.dart @@ -296,7 +296,6 @@ class _MorseTextInputState extends State // Morse preview (optional) if (widget.showMorsePreview) ...[ Container( - height: 80, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], @@ -306,6 +305,7 @@ class _MorseTextInputState extends State ), ), child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( @@ -326,49 +326,47 @@ class _MorseTextInputState extends State ), ], ), - const SizedBox(height: 4), - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - Text( - currentMorse.isEmpty - ? 'Tap below to input...' - : currentMorse, - style: TextStyle( - fontSize: 16, - fontFamily: 'monospace', - fontWeight: FontWeight.w600, - color: currentMorse.isEmpty - ? Colors.grey[500] - : Colors.black87, - ), + const SizedBox(height: 6), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Text( + currentMorse.isEmpty + ? 'Tap below to input...' + : currentMorse, + style: TextStyle( + fontSize: 15, + fontFamily: 'monospace', + fontWeight: FontWeight.w600, + color: currentMorse.isEmpty + ? Colors.grey[500] + : Colors.black87, ), - if (letterPreview.isNotEmpty) ...[ - const SizedBox(width: 12), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - color: Colors.blue[50], - border: Border.all(color: Colors.blue[200]!), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - letterPreview, - style: TextStyle( - fontSize: 12, - color: Colors.blue[700], - fontFamily: 'monospace', - ), + ), + if (letterPreview.isNotEmpty) ...[ + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.blue[50], + border: Border.all(color: Colors.blue[200]!), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + letterPreview, + style: TextStyle( + fontSize: 11, + color: Colors.blue[700], + fontFamily: 'monospace', ), ), - ], + ), ], - ), + ], ), ), ], From 8b5a52693e77259d902cdd0428ad8b972600622a Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 18:57:47 +0530 Subject: [PATCH 08/18] docs(morse_tap): add haptic config modal and update documentation - Move HapticConfigModal to example as reference implementation - Add haptic configuration UI to example apps - Update README with haptic feedback features and usage examples - Update LICENSE file --- packages/morse_tap/LICENSE | 22 +- packages/morse_tap/README.md | 121 +++----- .../example/lib/haptic_config_modal.dart | 276 ++++++++++++++++++ packages/morse_tap/example/lib/main.dart | 33 +++ packages/morse_tap/example/main.dart | 33 +++ 5 files changed, 398 insertions(+), 87 deletions(-) create mode 100644 packages/morse_tap/example/lib/haptic_config_modal.dart diff --git a/packages/morse_tap/LICENSE b/packages/morse_tap/LICENSE index ba75c69..a415433 100644 --- a/packages/morse_tap/LICENSE +++ b/packages/morse_tap/LICENSE @@ -1 +1,21 @@ -TODO: Add your license here. +MIT License + +Copyright (c) 2025 NonStop IO + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/morse_tap/README.md b/packages/morse_tap/README.md index de15ca3..086bfa4 100644 --- a/packages/morse_tap/README.md +++ b/packages/morse_tap/README.md @@ -25,35 +25,8 @@ A Flutter package that provides Morse code input functionality using intuitive g 🎯 **MorseTextInput** - Real-time gesture-to-text conversion widget 🔄 **String Extensions** - Convert any string to/from Morse code ⚡ **Fast Algorithm** - Efficient Morse code conversion with comprehensive character support -🎨 **Intuitive Gestures** - Single tap = dot, double tap = dash, long press = space - -## Requirements - -- Flutter >=3.19.0 -- Dart >=3.3.0 <4.0.0 - -## Installation - -Add to your `pubspec.yaml`: - -```yaml -dependencies: - morse_tap: ^0.0.1 -``` - -Or install via command line: - -```bash -flutter pub add morse_tap -``` - -## Quick Start - -Import the package: - -```dart -import 'package:morse_tap/morse_tap.dart'; -``` +🎨 **Intuitive Gestures** - Single tap = dot, double tap = dash, long press = space +📳 **Haptic Feedback** - Customizable tactile feedback for enhanced user experience ## Usage Examples @@ -173,6 +146,36 @@ MorseTapDetector( Note: The timeout resets after each input, allowing users to take their time with long sequences as long as they keep entering characters. +### Haptic Feedback + +Provide tactile feedback for gestures: + +```dart +MorseTapDetector( + expectedMorseCode: "... --- ...", + hapticConfig: HapticConfig.defaultConfig, // Enable haptic feedback + onCorrectSequence: () => print("Correct!"), + child: MyButton(), +) +``` + +**Preset configurations:** +```dart +// Different preset options +HapticConfig.disabled // No haptic feedback +HapticConfig.light // Subtle feedback +HapticConfig.defaultConfig // Moderate feedback +HapticConfig.strong // Intense feedback + +// Custom configuration +HapticConfig( + enabled: true, + dotIntensity: HapticFeedbackType.lightImpact, + dashIntensity: HapticFeedbackType.mediumImpact, + correctSequenceIntensity: HapticFeedbackType.heavyImpact, +) +``` + ### Visual Feedback Control visual feedback options: @@ -194,19 +197,6 @@ The package supports: - **Numbers**: 0-9 (10 digits) - **Punctuation**: . , ? ' ! / ( ) & : ; = + - _ " $ @ -## Morse Code Reference - -| Character | Morse Code | -|-----------|------------| -| A | .- | -| B | -... | -| C | -.-. | -| S | ... | -| O | --- | -| 0 | ----- | -| 1 | .---- | -| 9 | ----. | - *See the complete mapping in `MorseCodec` class documentation.* ## Advanced Usage @@ -231,37 +221,6 @@ MorseTapDetector( ) ``` -### Multiple Pattern Detection - -Handle different patterns: - -```dart -class MultiPatternDetector extends StatelessWidget { - final Map patterns = { - 'SOS': '... --- ...', - 'OK': '--- -.-', - 'YES': '-.-- . ...', - }; - - @override - Widget build(BuildContext context) { - return Column( - children: patterns.entries.map((entry) { - return MorseTapDetector( - expectedMorseCode: entry.value, - onCorrectSequence: () => handlePattern(entry.key), - child: PatternButton(label: entry.key), - ); - }).toList(), - ); - } - - void handlePattern(String pattern) { - print("Pattern $pattern detected!"); - } -} -``` - ## Contributing We welcome contributions in various forms: @@ -272,18 +231,8 @@ We welcome contributions in various forms: - Improving documentation, as it is essential. - Sending Pull Requests is greatly appreciated! -A big thank you to all our contributors! 🙌 - -

-
- --- -## 🔗 Connect with NonStop
@@ -301,7 +250,7 @@ A big thank you to all our contributors! 🙌
-> ⭐ Star us on [GitHub](https://github.com/nonstopio/flutter_forge) if this helped you! + ⭐ Star us on [GitHub](https://github.com/nonstopio/flutter_forge) if this helped you!
@@ -311,6 +260,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
-> 🎉 [Founded by Ajay Kumar](https://github.com/ProjectAJ14) 🎉** + 🎉 [Founded by Ajay Kumar](https://github.com/ProjectAJ14) 🎉** -
\ No newline at end of file +
diff --git a/packages/morse_tap/example/lib/haptic_config_modal.dart b/packages/morse_tap/example/lib/haptic_config_modal.dart new file mode 100644 index 0000000..1c3e858 --- /dev/null +++ b/packages/morse_tap/example/lib/haptic_config_modal.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:morse_tap/morse_tap.dart'; + +/// A modal dialog for configuring haptic feedback settings. +/// +/// Provides an intuitive interface for users to customize haptic feedback +/// intensity for different Morse code gestures and events. +class HapticConfigModal extends StatefulWidget { + /// Creates a haptic configuration modal. + const HapticConfigModal({ + super.key, + required this.initialConfig, + this.onConfigChanged, + }); + + /// The initial haptic configuration to display + final HapticConfig initialConfig; + + /// Callback when configuration changes are applied + final ValueChanged? onConfigChanged; + + @override + State createState() => _HapticConfigModalState(); + + /// Shows the haptic configuration modal. + /// + /// Returns the new configuration if saved, or null if canceled. + static Future show( + BuildContext context, { + required HapticConfig initialConfig, + }) { + return showDialog( + context: context, + builder: (context) => HapticConfigModal(initialConfig: initialConfig), + ); + } +} + +class _HapticConfigModalState extends State { + late HapticConfig _currentConfig; + String? _selectedPreset; + + @override + void initState() { + super.initState(); + _currentConfig = widget.initialConfig; + _selectedPreset = HapticUtils.getPresetName(_currentConfig); + } + + void _updateConfig(HapticConfig newConfig) { + setState(() { + _currentConfig = newConfig; + _selectedPreset = HapticUtils.getPresetName(newConfig); + }); + } + + void _applyPreset(String presetName) { + final preset = HapticUtils.presetConfigs[presetName]; + if (preset != null) { + _updateConfig(preset); + } + } + + void _resetToDefaults() { + _updateConfig(HapticConfig.defaultConfig); + } + + void _saveAndClose() { + widget.onConfigChanged?.call(_currentConfig); + Navigator.of(context).pop(_currentConfig); + } + + void _cancel() { + Navigator.of(context).pop(); + } + + Widget _buildEnabledSwitch() { + return SwitchListTile( + title: const Text('Enable Haptic Feedback'), + subtitle: Text( + HapticUtils.isHapticSupported + ? 'Provide tactile feedback for gestures' + : 'Not supported on this platform', + ), + value: _currentConfig.enabled, + onChanged: HapticUtils.isHapticSupported + ? (enabled) => + _updateConfig(_currentConfig.copyWith(enabled: enabled)) + : null, + ); + } + + Widget _buildPresetSelector() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Quick Presets', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + children: HapticUtils.presetConfigs.entries.map((entry) { + final isSelected = _selectedPreset == entry.key; + return FilterChip( + label: Text(entry.key), + selected: isSelected, + onSelected: (_) => _applyPreset(entry.key), + ); + }).toList(), + ), + ], + ), + ), + ); + } + + Widget _buildHapticSetting({ + required String title, + required String description, + required HapticFeedbackType currentType, + required ValueChanged onChanged, + }) { + return Card( + margin: const EdgeInsets.only(bottom: 8.0), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + Text( + description, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ), + ), + IconButton( + icon: const Icon(Icons.touch_app, size: 20), + tooltip: 'Test haptic', + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + onPressed: + _currentConfig.enabled && HapticUtils.isHapticSupported + ? () => HapticUtils.testHaptic(currentType) + : null, + ), + ], + ), + const SizedBox(height: 8), + DropdownButtonFormField( + initialValue: currentType, + decoration: const InputDecoration( + border: OutlineInputBorder(), + isDense: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + items: HapticUtils.availableHapticTypes + .map(HapticUtils.createDropdownItem) + .toList(), + onChanged: _currentConfig.enabled ? onChanged : null, + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Haptic Feedback Settings'), + contentPadding: const EdgeInsets.fromLTRB(24.0, 16.0, 24.0, 0.0), + content: SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height * 0.6, // Constrain height + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildEnabledSwitch(), + const SizedBox(height: 12), + + if (_currentConfig.enabled) ...[ + _buildPresetSelector(), + const SizedBox(height: 12), + + _buildHapticSetting( + title: 'Dots (Single Tap)', + description: 'Haptic feedback for dot inputs', + currentType: _currentConfig.dotIntensity, + onChanged: (type) => _updateConfig( + _currentConfig.copyWith(dotIntensity: type), + ), + ), + + _buildHapticSetting( + title: 'Dashes (Double Tap)', + description: 'Haptic feedback for dash inputs', + currentType: _currentConfig.dashIntensity, + onChanged: (type) => _updateConfig( + _currentConfig.copyWith(dashIntensity: type), + ), + ), + + _buildHapticSetting( + title: 'Spaces (Long Press)', + description: 'Haptic feedback for space inputs', + currentType: _currentConfig.spaceIntensity, + onChanged: (type) => _updateConfig( + _currentConfig.copyWith(spaceIntensity: type), + ), + ), + + _buildHapticSetting( + title: 'Correct Sequence', + description: 'Haptic feedback for successful completion', + currentType: _currentConfig.correctSequenceIntensity, + onChanged: (type) => _updateConfig( + _currentConfig.copyWith(correctSequenceIntensity: type), + ), + ), + + _buildHapticSetting( + title: 'Incorrect Sequence', + description: 'Haptic feedback for errors', + currentType: _currentConfig.incorrectSequenceIntensity, + onChanged: (type) => _updateConfig( + _currentConfig.copyWith(incorrectSequenceIntensity: type), + ), + ), + + _buildHapticSetting( + title: 'Input Timeout', + description: 'Haptic feedback for timeout events', + currentType: _currentConfig.timeoutIntensity, + onChanged: (type) => _updateConfig( + _currentConfig.copyWith(timeoutIntensity: type), + ), + ), + ], + ], + ), + ), + ), + actions: [ + TextButton(onPressed: _resetToDefaults, child: const Text('Reset')), + TextButton(onPressed: _cancel, child: const Text('Cancel')), + ElevatedButton(onPressed: _saveAndClose, child: const Text('Save')), + ], + ); + } +} diff --git a/packages/morse_tap/example/lib/main.dart b/packages/morse_tap/example/lib/main.dart index f5daf40..b4dd7d7 100644 --- a/packages/morse_tap/example/lib/main.dart +++ b/packages/morse_tap/example/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:morse_tap/morse_tap.dart'; +import 'haptic_config_modal.dart'; void main() { runApp(const MorseTapExampleApp()); @@ -105,6 +106,7 @@ class _MorseTapDetectorExampleState extends State { Color _buttonColor = Colors.blue; String _gestureHint = ''; String _currentSequence = ''; + HapticConfig _hapticConfig = HapticConfig.defaultConfig; final Map _targets = { 'SOS': '... --- ...', @@ -186,6 +188,19 @@ class _MorseTapDetectorExampleState extends State { }); } + void _showHapticConfig() async { + final newConfig = await HapticConfigModal.show( + context, + initialConfig: _hapticConfig, + ); + + if (newConfig != null) { + setState(() { + _hapticConfig = newConfig; + }); + } + } + @override Widget build(BuildContext context) { return Padding( @@ -281,12 +296,30 @@ class _MorseTapDetectorExampleState extends State { ), ), + const SizedBox(height: 16), + + // Haptic configuration button + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _showHapticConfig, + icon: const Icon(Icons.vibration), + label: Text( + 'Haptic Settings ${_hapticConfig.enabled ? "(Enabled)" : "(Disabled)"}', + ), + ), + ), + ], + ), + const SizedBox(height: 24), // Morse tap detector Expanded( child: MorseTapDetector( expectedMorseCode: _targets[_currentTarget]!, + hapticConfig: _hapticConfig, onCorrectSequence: _onCorrectSequence, onIncorrectSequence: _onIncorrectSequence, onInputTimeout: _onTimeout, diff --git a/packages/morse_tap/example/main.dart b/packages/morse_tap/example/main.dart index 6b1fc49..0a38ac5 100644 --- a/packages/morse_tap/example/main.dart +++ b/packages/morse_tap/example/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:morse_tap/morse_tap.dart'; +import 'lib/haptic_config_modal.dart'; void main() { runApp(const MorseTapExampleApp()); @@ -105,6 +106,7 @@ class _MorseTapDetectorExampleState extends State { Color _buttonColor = Colors.blue; String _gestureHint = ''; String _currentSequence = ''; + HapticConfig _hapticConfig = HapticConfig.defaultConfig; final Map _targets = { 'SOS': '... --- ...', @@ -186,6 +188,19 @@ class _MorseTapDetectorExampleState extends State { }); } + void _showHapticConfig() async { + final newConfig = await HapticConfigModal.show( + context, + initialConfig: _hapticConfig, + ); + + if (newConfig != null) { + setState(() { + _hapticConfig = newConfig; + }); + } + } + @override Widget build(BuildContext context) { return Padding( @@ -274,12 +289,30 @@ class _MorseTapDetectorExampleState extends State { ), ), + const SizedBox(height: 16), + + // Haptic configuration button + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _showHapticConfig, + icon: const Icon(Icons.vibration), + label: Text( + 'Haptic Settings ${_hapticConfig.enabled ? "(Enabled)" : "(Disabled)"}', + ), + ), + ), + ], + ), + const SizedBox(height: 24), // Morse tap detector Expanded( child: MorseTapDetector( expectedMorseCode: _targets[_currentTarget]!, + hapticConfig: _hapticConfig, onCorrectSequence: _onCorrectSequence, onIncorrectSequence: _onIncorrectSequence, onInputTimeout: _onTimeout, From fafd82ee3da8f0211be3dfab68e503e8c3023b78 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:05:05 +0530 Subject: [PATCH 09/18] test(morse_tap): fix example widget test - Fix test to use correct MorseTapExampleApp class instead of MyApp - Remove unused import to resolve linter warning - Update test to verify app loads correctly --- packages/morse_tap/example/test/widget_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/morse_tap/example/test/widget_test.dart diff --git a/packages/morse_tap/example/test/widget_test.dart b/packages/morse_tap/example/test/widget_test.dart new file mode 100644 index 0000000..8452607 --- /dev/null +++ b/packages/morse_tap/example/test/widget_test.dart @@ -0,0 +1,16 @@ +// Basic widget test for Morse Tap Example app. + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:morse_tap_example/main.dart'; + +void main() { + testWidgets('Morse Tap Example app smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MorseTapExampleApp()); + + // Verify that the app loads with expected elements + expect(find.text('Morse Tap Example'), findsOneWidget); + expect(find.text('Tap Detector'), findsOneWidget); + }); +} From 9dfe80470743d54423debd3b8622b7047052ee2c Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:11:59 +0530 Subject: [PATCH 10/18] fix(morse_tap): resolve UI overflow in tap detector example - Reduce spacing between elements to prevent 62px overflow - Optimize tap detector button layout with smaller icon and text - Add mainAxisSize.min to Column to prevent overflow - Reduce padding and font sizes in status message - Improve overall layout compactness --- packages/morse_tap/example/lib/main.dart | 37 +++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/morse_tap/example/lib/main.dart b/packages/morse_tap/example/lib/main.dart index b4dd7d7..1c42c91 100644 --- a/packages/morse_tap/example/lib/main.dart +++ b/packages/morse_tap/example/lib/main.dart @@ -313,7 +313,7 @@ class _MorseTapDetectorExampleState extends State { ], ), - const SizedBox(height: 24), + const SizedBox(height: 16), // Morse tap detector Expanded( @@ -344,24 +344,26 @@ class _MorseTapDetectorExampleState extends State { ), ], ), - child: const Center( + child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.touch_app, size: 48, color: Colors.white), - SizedBox(height: 12), - Text( + Icon(Icons.touch_app, size: 40, color: Colors.white), + const SizedBox(height: 8), + const Text( 'TAP HERE', style: TextStyle( color: Colors.white, - fontSize: 24, + fontSize: 20, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 8), - Text( + const SizedBox(height: 6), + const Text( '1 tap = • | 2 taps = — | Hold = space', - style: TextStyle(color: Colors.white70, fontSize: 14), + style: TextStyle(color: Colors.white70, fontSize: 12), + textAlign: TextAlign.center, ), ], ), @@ -370,38 +372,39 @@ class _MorseTapDetectorExampleState extends State { ), ), - const SizedBox(height: 16), + const SizedBox(height: 12), // Status message Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Column( + mainAxisSize: MainAxisSize.min, children: [ Text( _message, textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16), + style: const TextStyle(fontSize: 15), ), if (_gestureHint.isNotEmpty) ...[ - const SizedBox(height: 8), + const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, + horizontal: 10, + vertical: 3, ), decoration: BoxDecoration( color: Colors.blue[50], border: Border.all(color: Colors.blue[200]!), - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(12), ), child: Text( _gestureHint, style: TextStyle( - fontSize: 12, + fontSize: 11, color: Colors.blue[700], fontWeight: FontWeight.w500, ), From 5e7e8b7b5141e4e27cc6f9e1caa0ea20854e1c03 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:16:43 +0530 Subject: [PATCH 11/18] refactor(morse_tap): update app title and improve layout with ListView --- packages/morse_tap/example/.metadata | 30 ++++++++++++------------ packages/morse_tap/example/lib/main.dart | 28 ++++++---------------- 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/packages/morse_tap/example/.metadata b/packages/morse_tap/example/.metadata index 6a623a4..d1512eb 100644 --- a/packages/morse_tap/example/.metadata +++ b/packages/morse_tap/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "d7b523b356d15fb81e7d340bbe52b47f93937323" + revision: "b8962555571d8c170cff8e76023ea7bf60e5ec4b" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b - platform: android - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b - platform: ios - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b - platform: linux - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b - platform: macos - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b - platform: web - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b - platform: windows - create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + create_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b + base_revision: b8962555571d8c170cff8e76023ea7bf60e5ec4b # User provided section diff --git a/packages/morse_tap/example/lib/main.dart b/packages/morse_tap/example/lib/main.dart index 1c42c91..d7ff5b1 100644 --- a/packages/morse_tap/example/lib/main.dart +++ b/packages/morse_tap/example/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:morse_tap/morse_tap.dart'; + import 'haptic_config_modal.dart'; void main() { @@ -12,7 +13,7 @@ class MorseTapExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Morse Tap Example', + title: 'Morse Tap Detector', theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true), home: const HomePage(), ); @@ -205,16 +206,8 @@ class _MorseTapDetectorExampleState extends State { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + child: ListView( children: [ - MorseTapDetector( - expectedMorseCode: '...', - onCorrectSequence: () {}, - child: CircleAvatar(), - ), - const SizedBox(height: 16), - Card( child: Padding( padding: const EdgeInsets.all(16.0), @@ -443,8 +436,7 @@ class _MorseTextInputExampleState extends State { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + child: ListView( children: [ Card( child: Padding( @@ -573,21 +565,15 @@ class _StringExtensionExampleState extends State { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + padding: const EdgeInsets.all(8.0), + child: ListView( children: [ Card( child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(8.0), child: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'String Extensions', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), Text( 'Demonstrates the extension methods available on String for Morse code conversion.', ), From 57e8660932fd0b0f0ce12e2a348574b08da83798 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:17:35 +0530 Subject: [PATCH 12/18] chore(release): publish packages - morse_tap@0.0.2 --- packages/morse_tap/CHANGELOG.md | 13 +++++++++++++ packages/morse_tap/pubspec.yaml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/morse_tap/CHANGELOG.md b/packages/morse_tap/CHANGELOG.md index 41cc7d8..cb17775 100644 --- a/packages/morse_tap/CHANGELOG.md +++ b/packages/morse_tap/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.0.2 + + - **REFACTOR**(morse_tap): update app title and improve layout with ListView. + - **REFACTOR**(morse_tap): clean up morse_text_input formatting. + - **REFACTOR**(morse_tap): remove visual feedback from MorseTapDetector. + - **FIX**(morse_tap): resolve UI overflow in tap detector example. + - **FEAT**(morse_tap): integrate haptic feedback and fix UI overflow. + - **FEAT**(morse_tap): add haptic feedback system. + - **FEAT**(examples): integrate onSequenceChange callback with real-time UI. + - **FEAT**(packages): add morse_tap package with gesture-based input. + - **DOCS**(morse_tap): add haptic config modal and update documentation. + - **DOCS**(morse_tap): add screenshot and improve documentation. + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/morse_tap/pubspec.yaml b/packages/morse_tap/pubspec.yaml index 8e13a1b..ad9e37a 100644 --- a/packages/morse_tap/pubspec.yaml +++ b/packages/morse_tap/pubspec.yaml @@ -1,6 +1,6 @@ name: morse_tap description: "A Flutter package for Morse code input using intuitive gestures. Detect patterns, convert text in real-time, and create interactive Morse experiences." -version: 0.0.1 +version: 0.0.2 homepage: https://github.com/nonstopio/flutter_forge/tree/main/packages/morse_tap repository: https://github.com/nonstopio/flutter_forge From 0d39f340e65f533f760351133bf78854b650bd26 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:21:45 +0530 Subject: [PATCH 13/18] docs: add morse_tap package to README files - Add morse_tap to packages/README.md with pub.dev badge and link - Add morse_tap to root README.md in packages table - Include proper formatting and styling consistent with other packages --- README.md | 3 ++- packages/README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ab0e253..8d38277 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ A place where Flutter packages are crafted and built. | [![ns_utils](https://img.shields.io/pub/v/ns_utils.svg?label=ns_utils&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/ns_utils) | | [![ns_intl_phone_input](https://img.shields.io/pub/v/ns_intl_phone_input.svg?label=ns_intl_phone_input&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/ns_intl_phone_input) | | [![dzod](https://img.shields.io/pub/v/dzod.svg?label=dzod&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/dzod) | -| [![html_rich_text](https://img.shields.io/pub/v/html_rich_text.svg?label=html_rich_text&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/html_rich_text) | +| [![html_rich_text](https://img.shields.io/pub/v/html_rich_text.svg?label=html_rich_text&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/html_rich_text) | +| [![morse_tap](https://img.shields.io/pub/v/morse_tap.svg?label=morse_tap&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/morse_tap) | | Plugins | |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/packages/README.md b/packages/README.md index 99eb520..d324fe5 100644 --- a/packages/README.md +++ b/packages/README.md @@ -23,3 +23,4 @@ | ns_intl_phone_input | [![ns_intl_phone_input pub.dev badge](https://img.shields.io/pub/v/ns_intl_phone_input.svg)](https://pub.dev/packages/ns_intl_phone_input) | [`🔗`](ns_intl_phone_input/README.md) | | html_rich_text | [![html_rich_text pub.dev badge](https://img.shields.io/pub/v/html_rich_text.svg)](https://pub.dev/packages/html_rich_text) | [`🔗`](html_rich_text/README.md) | | dzod | [![dzod pub.dev badge](https://img.shields.io/pub/v/dzod.svg)](https://pub.dev/packages/dzod) | [`🔗`](dzod/README.md) | +| morse_tap | [![morse_tap pub.dev badge](https://img.shields.io/pub/v/morse_tap.svg)](https://pub.dev/packages/morse_tap) | [`🔗`](morse_tap/README.md) | From fb54ead301a3cf9251e30bf334dd60390c4685b8 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:37:14 +0530 Subject: [PATCH 14/18] feat(morse_tap): add comprehensive platform support for web and desktop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add conditional platform detection for web, iOS, Android, Linux, macOS, Windows - Replace direct dart:io import with conditional imports for web compatibility - Create platform_utils_io.dart for native platforms (iOS/Android with haptic support) - Create platform_utils_web.dart for web platform (haptic disabled) - Update pubspec.yaml to declare support for all Flutter platforms - Maintain backward compatibility while enabling cross-platform usage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/morse_tap/lib/src/utils/haptic_utils.dart | 4 ++-- packages/morse_tap/lib/src/utils/platform_utils_io.dart | 9 +++++++++ packages/morse_tap/lib/src/utils/platform_utils_web.dart | 6 ++++++ packages/morse_tap/pubspec.yaml | 8 ++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 packages/morse_tap/lib/src/utils/platform_utils_io.dart create mode 100644 packages/morse_tap/lib/src/utils/platform_utils_web.dart diff --git a/packages/morse_tap/lib/src/utils/haptic_utils.dart b/packages/morse_tap/lib/src/utils/haptic_utils.dart index ceb2890..7922ef3 100644 --- a/packages/morse_tap/lib/src/utils/haptic_utils.dart +++ b/packages/morse_tap/lib/src/utils/haptic_utils.dart @@ -1,9 +1,9 @@ -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/haptic_config.dart'; import '../models/haptic_feedback_type.dart'; +import 'platform_utils_io.dart' if (dart.library.html) 'platform_utils_web.dart'; /// Utility class for managing haptic feedback in Morse code widgets. /// @@ -42,7 +42,7 @@ class HapticUtils { /// Checks if haptic feedback is supported on the current platform static bool get isHapticSupported { if (kIsWeb) return false; - return Platform.isIOS || Platform.isAndroid; + return PlatformUtils.isHapticSupported; } /// Safely executes haptic feedback with error handling diff --git a/packages/morse_tap/lib/src/utils/platform_utils_io.dart b/packages/morse_tap/lib/src/utils/platform_utils_io.dart new file mode 100644 index 0000000..470c96a --- /dev/null +++ b/packages/morse_tap/lib/src/utils/platform_utils_io.dart @@ -0,0 +1,9 @@ +import 'dart:io'; + +/// Platform utilities for native platforms (mobile and desktop) +class PlatformUtils { + /// Checks if haptic feedback is supported on the current platform + /// Returns true for iOS and Android platforms + /// Desktop platforms (Linux, macOS, Windows) don't support haptic feedback + static bool get isHapticSupported => Platform.isIOS || Platform.isAndroid; +} \ No newline at end of file diff --git a/packages/morse_tap/lib/src/utils/platform_utils_web.dart b/packages/morse_tap/lib/src/utils/platform_utils_web.dart new file mode 100644 index 0000000..f68b379 --- /dev/null +++ b/packages/morse_tap/lib/src/utils/platform_utils_web.dart @@ -0,0 +1,6 @@ +/// Platform utilities for web platform +class PlatformUtils { + /// Checks if haptic feedback is supported on the current platform + /// Always returns false for web platform + static bool get isHapticSupported => false; +} \ No newline at end of file diff --git a/packages/morse_tap/pubspec.yaml b/packages/morse_tap/pubspec.yaml index ad9e37a..5d6f185 100644 --- a/packages/morse_tap/pubspec.yaml +++ b/packages/morse_tap/pubspec.yaml @@ -12,6 +12,14 @@ environment: sdk: ^3.8.1 flutter: ">=1.17.0" +platforms: + android: + ios: + web: + linux: + macos: + windows: + dependencies: flutter: sdk: flutter From 718535e39c71c0a2051ff1a8fcb0eb54c9be6735 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:37:50 +0530 Subject: [PATCH 15/18] refactor(haptic_utils): improve import formatting and ensure newline at EOF --- README.md | 2 +- packages/morse_tap/lib/src/utils/haptic_utils.dart | 3 ++- packages/morse_tap/lib/src/utils/platform_utils_io.dart | 2 +- packages/morse_tap/lib/src/utils/platform_utils_web.dart | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d38277..2858b19 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ A place where Flutter packages are crafted and built. | [![ns_intl_phone_input](https://img.shields.io/pub/v/ns_intl_phone_input.svg?label=ns_intl_phone_input&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/ns_intl_phone_input) | | [![dzod](https://img.shields.io/pub/v/dzod.svg?label=dzod&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/dzod) | | [![html_rich_text](https://img.shields.io/pub/v/html_rich_text.svg?label=html_rich_text&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/html_rich_text) | -| [![morse_tap](https://img.shields.io/pub/v/morse_tap.svg?label=morse_tap&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/morse_tap) | +| [![morse_tap](https://img.shields.io/pub/v/morse_tap.svg?label=morse_tap&logo=dart&color=blue&style=for-the-badge)](https://pub.dev/packages/morse_tap) | | Plugins | |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/packages/morse_tap/lib/src/utils/haptic_utils.dart b/packages/morse_tap/lib/src/utils/haptic_utils.dart index 7922ef3..2241126 100644 --- a/packages/morse_tap/lib/src/utils/haptic_utils.dart +++ b/packages/morse_tap/lib/src/utils/haptic_utils.dart @@ -3,7 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/haptic_config.dart'; import '../models/haptic_feedback_type.dart'; -import 'platform_utils_io.dart' if (dart.library.html) 'platform_utils_web.dart'; +import 'platform_utils_io.dart' + if (dart.library.html) 'platform_utils_web.dart'; /// Utility class for managing haptic feedback in Morse code widgets. /// diff --git a/packages/morse_tap/lib/src/utils/platform_utils_io.dart b/packages/morse_tap/lib/src/utils/platform_utils_io.dart index 470c96a..b39d285 100644 --- a/packages/morse_tap/lib/src/utils/platform_utils_io.dart +++ b/packages/morse_tap/lib/src/utils/platform_utils_io.dart @@ -6,4 +6,4 @@ class PlatformUtils { /// Returns true for iOS and Android platforms /// Desktop platforms (Linux, macOS, Windows) don't support haptic feedback static bool get isHapticSupported => Platform.isIOS || Platform.isAndroid; -} \ No newline at end of file +} diff --git a/packages/morse_tap/lib/src/utils/platform_utils_web.dart b/packages/morse_tap/lib/src/utils/platform_utils_web.dart index f68b379..192b256 100644 --- a/packages/morse_tap/lib/src/utils/platform_utils_web.dart +++ b/packages/morse_tap/lib/src/utils/platform_utils_web.dart @@ -3,4 +3,4 @@ class PlatformUtils { /// Checks if haptic feedback is supported on the current platform /// Always returns false for web platform static bool get isHapticSupported => false; -} \ No newline at end of file +} From 01c309d709173d15b4b5db7a49faddbe11c7c39c Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Fri, 22 Aug 2025 19:38:13 +0530 Subject: [PATCH 16/18] chore(release): publish packages - morse_tap@0.0.3 --- packages/morse_tap/CHANGELOG.md | 5 +++++ packages/morse_tap/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/morse_tap/CHANGELOG.md b/packages/morse_tap/CHANGELOG.md index cb17775..f3ac5a3 100644 --- a/packages/morse_tap/CHANGELOG.md +++ b/packages/morse_tap/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.0.3 + + - **REFACTOR**(haptic_utils): improve import formatting and ensure newline at EOF. + - **FEAT**(morse_tap): add comprehensive platform support for web and desktop. + ## 0.0.2 - **REFACTOR**(morse_tap): update app title and improve layout with ListView. diff --git a/packages/morse_tap/pubspec.yaml b/packages/morse_tap/pubspec.yaml index 5d6f185..17858ed 100644 --- a/packages/morse_tap/pubspec.yaml +++ b/packages/morse_tap/pubspec.yaml @@ -1,6 +1,6 @@ name: morse_tap description: "A Flutter package for Morse code input using intuitive gestures. Detect patterns, convert text in real-time, and create interactive Morse experiences." -version: 0.0.2 +version: 0.0.3 homepage: https://github.com/nonstopio/flutter_forge/tree/main/packages/morse_tap repository: https://github.com/nonstopio/flutter_forge From f49324360bdacd1dc7add8ab1c075bdfd0cb4735 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Wed, 27 Aug 2025 21:54:39 +0530 Subject: [PATCH 17/18] fix: remove test from morse_tap example --- packages/morse_tap/example/test/widget_test.dart | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 packages/morse_tap/example/test/widget_test.dart diff --git a/packages/morse_tap/example/test/widget_test.dart b/packages/morse_tap/example/test/widget_test.dart deleted file mode 100644 index 8452607..0000000 --- a/packages/morse_tap/example/test/widget_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Basic widget test for Morse Tap Example app. - -import 'package:flutter_test/flutter_test.dart'; - -import 'package:morse_tap_example/main.dart'; - -void main() { - testWidgets('Morse Tap Example app smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MorseTapExampleApp()); - - // Verify that the app loads with expected elements - expect(find.text('Morse Tap Example'), findsOneWidget); - expect(find.text('Tap Detector'), findsOneWidget); - }); -} From 75217ef5b684abcb7ae46fd5d2ce6af2a86aa10b Mon Sep 17 00:00:00 2001 From: Ajay Kumar Date: Wed, 27 Aug 2025 22:10:48 +0530 Subject: [PATCH 18/18] chore: update Flutter version to 3.35.0 in CI configuration --- .github/workflows/dart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index ccab1f7..fa41eac 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -7,7 +7,7 @@ on: branches: [ "main" ] env: - FLUTTER_VERSION: "3.32.3" + FLUTTER_VERSION: "3.35.0" jobs: discover: