words_numbers 0.1.0
words_numbers: ^0.1.0 copied to clipboard
High-performance Dart & Flutter library to convert number words to numbers ('five hundred twenty-two' -> 522) and numbers to words, with sentence parsing & currency support.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:words_numbers/words_numbers.dart';
void main() {
runApp(const WordsNumbersApp());
}
/// Root widget for the words_numbers interactive demo application.
class WordsNumbersApp extends StatelessWidget {
const WordsNumbersApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'WordsNumbers Interactive Playground',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF4F46E5),
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF4F46E5),
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const PlaygroundScreen(),
);
}
}
/// The main playground screen featuring Words-to-Numbers and Numbers-to-Words.
class PlaygroundScreen extends StatefulWidget {
const PlaygroundScreen({super.key});
@override
State<PlaygroundScreen> createState() => _PlaygroundScreenState();
}
class _PlaygroundScreenState extends State<PlaygroundScreen> {
// Controller for Tab 1 (Words to Numbers)
final TextEditingController _wordsController = TextEditingController(
text: 'hello world OnE \n How are you two',
);
// Controller for Tab 2 (Numbers to Words)
final TextEditingController _numberController = TextEditingController(
text: '1250.50',
);
bool _includeAnd = true;
bool _hyphenate = true;
bool _currencyMode = true;
@override
void dispose() {
_wordsController.dispose();
_numberController.dispose();
super.dispose();
}
void _copyToClipboard(String text, String label) {
Clipboard.setData(ClipboardData(text: text));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$label copied to clipboard!'),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
),
);
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('WordsNumbers Playground'),
centerTitle: true,
bottom: const TabBar(
tabs: [
Tab(
icon: Icon(Icons.text_fields),
text: 'Words → Numbers',
),
Tab(
icon: Icon(Icons.numbers),
text: 'Numbers → Words',
),
],
),
),
body: SafeArea(
child: TabBarView(
children: [
_buildWordsToNumbersTab(),
_buildNumbersToWordsTab(),
],
),
),
),
);
}
Widget _buildWordsToNumbersTab() {
final inputText = _wordsController.text;
final convertedSentence = WordsNumbers.convertTextNumberToString(inputText);
final parsedDirect = WordsNumbers.tryParse(inputText);
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Speech-to-Text & Sentence Parser',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
'Converts compound phrases and words in sentences to digits.',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 4,
children: [
_buildPresetChip('hello world OnE \n How are you two'),
_buildPresetChip('five hundred twenty-two'),
_buildPresetChip(
'I bought twenty-two apples for five hundred dollars.',
),
_buildPresetChip('The speed was three point one four meters.'),
_buildPresetChip(
'She won first place in the twenty-first event.'),
],
),
const SizedBox(height: 16),
TextField(
controller: _wordsController,
maxLines: 4,
decoration: InputDecoration(
labelText: 'Input Text / Words',
alignLabelWithHint: true,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_wordsController.clear();
});
},
),
),
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Converted Output:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
IconButton(
tooltip: 'Copy Output',
icon: const Icon(Icons.copy),
onPressed: convertedSentence.isNotEmpty
? () => _copyToClipboard(
convertedSentence,
'Converted output',
)
: null,
),
],
),
const Divider(),
const SizedBox(height: 8),
SelectableText(
convertedSentence.isNotEmpty
? convertedSentence
: '(Empty input)',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
),
if (parsedDirect != null) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Direct parsed value: $parsedDirect (${parsedDirect.runtimeType})',
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.primary,
),
),
),
],
],
),
),
),
],
),
);
}
Widget _buildNumbersToWordsTab() {
final rawNumber = _numberController.text.trim();
final parsedNum = num.tryParse(rawNumber);
String convertedOutput;
if (parsedNum == null) {
convertedOutput = rawNumber.isEmpty
? '(Enter a number above)'
: 'Please enter a valid numeric value.';
} else if (_currencyMode) {
convertedOutput = WordsNumbers.toCurrency(
parsedNum,
includeAnd: _includeAnd,
hyphenate: _hyphenate,
);
} else {
if (parsedNum is int) {
convertedOutput = WordsNumbers.toWords(
parsedNum,
includeAnd: _includeAnd,
hyphenate: _hyphenate,
);
} else {
// Double without currency: handle dollars/cents or integer conversion
convertedOutput = WordsNumbers.toCurrency(
parsedNum,
currencyWord: 'point',
centWord: '',
includeAnd: _includeAnd,
hyphenate: _hyphenate,
);
}
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Number to Words & Currency Engine',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
'Generates English words for invoices, receipts, and checks.',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 4,
children: [
_buildNumberChip('42'),
_buildNumberChip('105'),
_buildNumberChip('1250'),
_buildNumberChip('1250.50'),
_buildNumberChip('1500000'),
_buildNumberChip('-42.25'),
],
),
const SizedBox(height: 16),
TextField(
controller: _numberController,
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
signed: true,
),
decoration: InputDecoration(
labelText: 'Number Input',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_numberController.clear();
});
},
),
),
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 12),
Wrap(
spacing: 16,
children: [
FilterChip(
label: const Text('Currency Mode (\$)'),
selected: _currencyMode,
onSelected: (val) => setState(() => _currencyMode = val),
),
FilterChip(
label: const Text('Include "and"'),
selected: _includeAnd,
onSelected: (val) => setState(() => _includeAnd = val),
),
FilterChip(
label: const Text('Hyphenation'),
selected: _hyphenate,
onSelected: (val) => setState(() => _hyphenate = val),
),
],
),
const SizedBox(height: 16),
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'English Words Result:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
IconButton(
tooltip: 'Copy Output',
icon: const Icon(Icons.copy),
onPressed: parsedNum != null
? () => _copyToClipboard(
convertedOutput,
'English words',
)
: null,
),
],
),
const Divider(),
const SizedBox(height: 8),
SelectableText(
convertedOutput,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
],
),
);
}
Widget _buildPresetChip(String text) {
return ActionChip(
label: Text(
text.contains('\n') ? text.split('\n').first : text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
onPressed: () {
setState(() {
_wordsController.text = text;
});
},
);
}
Widget _buildNumberChip(String numberStr) {
return ActionChip(
label: Text(numberStr),
onPressed: () {
setState(() {
_numberController.text = numberStr;
});
},
);
}
}