morse_tap 0.0.3 copy "morse_tap: ^0.0.3" to clipboard
morse_tap: ^0.0.3 copied to clipboard

A Flutter package for Morse code input using intuitive gestures. Detect patterns, convert text in real-time, and create interactive Morse experiences.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:morse_tap/morse_tap.dart';

import 'haptic_config_modal.dart';

void main() {
  runApp(const MorseTapExampleApp());
}

class MorseTapExampleApp extends StatelessWidget {
  const MorseTapExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Morse Tap Detector',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _currentPage = 0;
  final PageController _pageController = PageController();

  final List<Widget> _pages = [
    const MorseTapDetectorExample(),
    const MorseTextInputExample(),
    const StringExtensionExample(),
  ];

  final List<String> _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<MorseTapDetectorExample> createState() =>
      _MorseTapDetectorExampleState();
}

class _MorseTapDetectorExampleState extends State<MorseTapDetectorExample> {
  String _message = 'Use gestures to input SOS (... --- ...)';
  String _currentTarget = 'SOS';
  Color _buttonColor = Colors.blue;
  String _gestureHint = '';
  String _currentSequence = '';
  HapticConfig _hapticConfig = HapticConfig.defaultConfig;

  final Map<String, String> _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';
    });
  }

  void _showHapticConfig() async {
    final newConfig = await HapticConfigModal.show(
      context,
      initialConfig: _hapticConfig,
    );

    if (newConfig != null) {
      setState(() {
        _hapticConfig = newConfig;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: ListView(
        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',
                    ),
                  ),
                  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,
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ),

          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 = '';
                          _currentSequence = '';
                        });
                      }
                    },
                  ),
                );
              }).toList(),
            ),
          ),

          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: 16),

          // Morse tap detector
          Expanded(
            child: MorseTapDetector(
              expectedMorseCode: _targets[_currentTarget]!,
              hapticConfig: _hapticConfig,
              onCorrectSequence: _onCorrectSequence,
              onIncorrectSequence: _onIncorrectSequence,
              onInputTimeout: _onTimeout,
              onSequenceChange: (sequence) {
                setState(() {
                  _currentSequence = sequence;
                });
              },
              onDotAdded: _onDotAdded,
              onDashAdded: _onDashAdded,
              onSpaceAdded: _onSpaceAdded,
              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: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.touch_app, size: 40, color: Colors.white),
                      const SizedBox(height: 8),
                      const Text(
                        'TAP HERE',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 6),
                      const Text(
                        '1 tap = • | 2 taps = — | Hold = space',
                        style: TextStyle(color: Colors.white70, fontSize: 12),
                        textAlign: TextAlign.center,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),

          const SizedBox(height: 12),

          // Status message
          Container(
            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: 15),
                ),
                if (_gestureHint.isNotEmpty) ...[
                  const SizedBox(height: 6),
                  Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 10,
                      vertical: 3,
                    ),
                    decoration: BoxDecoration(
                      color: Colors.blue[50],
                      border: Border.all(color: Colors.blue[200]!),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(
                      _gestureHint,
                      style: TextStyle(
                        fontSize: 11,
                        color: Colors.blue[700],
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                ],
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class MorseTextInputExample extends StatefulWidget {
  const MorseTextInputExample({super.key});

  @override
  State<MorseTextInputExample> createState() => _MorseTextInputExampleState();
}

class _MorseTextInputExampleState extends State<MorseTextInputExample> {
  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: ListView(
        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<StringExtensionExample> createState() => _StringExtensionExampleState();
}

class _StringExtensionExampleState extends State<StringExtensionExample> {
  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(8.0),
      child: ListView(
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: const Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  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()'),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
1
likes
160
points
9
downloads
screenshot

Publisher

verified publisherdart.nonstopio.com

Weekly Downloads

A Flutter package for Morse code input using intuitive gestures. Detect patterns, convert text in real-time, and create interactive Morse experiences.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on morse_tap