scientific_input 1.0.0 copy "scientific_input: ^1.0.0" to clipboard
scientific_input: ^1.0.0 copied to clipboard

Flutter widgets and utilities for parsing, validating, formatting, and displaying scientific notation such as 1.2e-3 and 1.2 × 10⁻³.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Scientific Input Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const ExampleHomePage(),
    );
  }
}

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

  @override
  State<ExampleHomePage> createState() => _ExampleHomePageState();
}

class _ExampleHomePageState extends State<ExampleHomePage> {
  final ScientificNumberController _inputController =
      ScientificNumberController(text: '1.2e-3');

  @override
  void dispose() {
    _inputController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Scientific Input'),
          bottom: const TabBar(
            tabAlignment: TabAlignment.center,
            tabs: [
              Tab(text: 'Input'),
              Tab(text: 'Text'),
              Tab(text: 'Tooltip'),
            ],
          ),
        ),
        body: SafeArea(
          child: Center(
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxWidth: 720),
              child: TabBarView(
                children: [
                  _InputShowcase(inputController: _inputController),
                  const _TextShowcase(),
                  const _TooltipShowcase(),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _InputShowcase extends StatelessWidget {
  const _InputShowcase({required this.inputController});

  final ScientificNumberController inputController;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: inputController,
      builder: (context, _) {
        return _ShowcaseScrollView(
          children: [
            const _IntroCard(
              title: 'Covers the common entry flow',
              description: 'The example focuses on the default typed-input experience first, since that is the most common way apps use the package.',
            ),
            _ShowcaseCard(
              title: 'Keep written input',
              description: 'Users can type standard notation like 1.2e-3 or paste readable text like 1.2 x 10^-3. The field keeps their written form and shows a readable preview underneath.',
              child: ScientificNumberInputField(
                controller: inputController,
                labelText: 'Value',
                hintText: 'e.g. -3.5e6',
                previewTooltipBuilder: (data) => data.value.toString(),
              ),
            ),
            _ControllerStateCard(
              title: 'Controller state',
              controller: inputController,
              rows: [
                _StateRowData(
                  label: 'Parsed double',
                  value: inputController.doubleValue?.toString() ?? 'null',
                ),
                _StateRowData(
                  label: 'Raw scientific',
                  value: inputController.rawScientificText ?? 'null',
                ),
                _StateRowData(
                  label: 'Readable text',
                  value: inputController.readableText ?? 'null',
                ),
              ],
            ),
          ],
        );
      },
    );
  }
}

class _TextShowcase extends StatelessWidget {
  const _TextShowcase();

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textStyle = theme.textTheme.titleMedium?.copyWith(
      fontFamily: 'monospace',
    );

    return _ShowcaseScrollView(
      children: [
        const _IntroCard(
          title: 'Display-only formatting',
          description: 'Use ScientificNumberText when you already have stored values and only need consistent readable output.',
        ),
        _ShowcaseCard(
          title: 'Static scientific values',
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _InfoRow(
                label: 'Selectable text',
                child: ScientificNumberText(
                  '1.2e-3',
                  selectable: true,
                  style: textStyle,
                ),
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'Plain number stays plain',
                child: ScientificNumberText('12', style: textStyle),
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'Invalid fallback',
                child: ScientificNumberText(
                  'abc',
                  invalidText: 'Invalid scientific value',
                  style: textStyle,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

class _TooltipShowcase extends StatelessWidget {
  const _TooltipShowcase();

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textStyle = theme.textTheme.titleMedium?.copyWith(
      fontFamily: 'monospace',
    );

    return _ShowcaseScrollView(
      children: [
        const _IntroCard(
          title: 'Tooltips and full-value display',
          description: 'Both the input preview and the display-only widget can expose the parsed decimal value for hover and long-press interactions.',
        ),
        _ShowcaseCard(
          title: 'Preview tooltip',
          child: ScientificNumberInputField(
            labelText: 'Tooltip preview',
            hintText: 'Try 5e6',
            showFormattedPreview: true,
            previewTooltipBuilder: (data) => data.value.toString(),
          ),
        ),
        _ShowcaseCard(
          title: 'Text tooltip',
          description: 'Hover or long-press the formatted text to reveal the full parsed value. This demo keeps the text non-selectable so the tooltip gesture stays available.',
          child: Align(
            alignment: Alignment.centerLeft,
            child: ScientificNumberText(
              '5e6',
              style: textStyle,
              tooltipBuilder: (data) => data.value.toString(),
            ),
          ),
        ),
      ],
    );
  }
}

class _ShowcaseScrollView extends StatelessWidget {
  const _ShowcaseScrollView({required this.children});

  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          for (var i = 0; i < children.length; i++) ...[
            children[i],
            if (i != children.length - 1) const SizedBox(height: 12),
          ],
        ],
      ),
    );
  }
}

class _IntroCard extends StatelessWidget {
  const _IntroCard({required this.title, required this.description});

  final String title;
  final String description;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Card(
      elevation: 0,
      color: theme.colorScheme.surfaceContainerHighest,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: theme.textTheme.titleLarge),
            const SizedBox(height: 8),
            Text(
              description,
              style: theme.textTheme.bodyMedium?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _ShowcaseCard extends StatelessWidget {
  const _ShowcaseCard({
    required this.title,
    this.description,
    required this.child,
  });

  final String title;
  final String? description;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Card(
      elevation: 0,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: theme.textTheme.titleMedium),
            if (description != null) ...[
              const SizedBox(height: 6),
              Text(
                description!,
                style: theme.textTheme.bodyMedium?.copyWith(
                  color: theme.colorScheme.onSurfaceVariant,
                ),
              ),
            ],
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }
}

class _ControllerStateCard extends StatelessWidget {
  const _ControllerStateCard({
    required this.title,
    required this.controller,
    required this.rows,
  });

  final String title;
  final ScientificNumberController controller;
  final List<_StateRowData> rows;

  @override
  Widget build(BuildContext context) {
    final errorText = controller.errorText;

    return _ShowcaseCard(
      title: title,
      child: DefaultTextStyle(
        style: Theme.of(context).textTheme.bodyMedium!,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            for (var i = 0; i < rows.length; i++) ...[
              _InfoRow(label: rows[i].label, value: rows[i].value),
              if (i != rows.length - 1) const SizedBox(height: 10),
            ],
            if (errorText != null) ...[
              const SizedBox(height: 10),
              _InfoRow(label: 'Error', value: errorText),
            ],
          ],
        ),
      ),
    );
  }
}

class _InfoRow extends StatelessWidget {
  const _InfoRow({required this.label, this.value, this.child})
    : assert(
        value != null || child != null,
        'Either value or child must be provided.',
      );

  final String label;
  final String? value;
  final Widget? child;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(
          width: 132,
          child: Text(
            label,
            style: theme.textTheme.bodySmall?.copyWith(
              color: theme.colorScheme.onSurfaceVariant,
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child:
              child ??
              SelectableText(
                value!,
                style: theme.textTheme.bodyMedium?.copyWith(
                  fontFamily: 'monospace',
                ),
              ),
        ),
      ],
    );
  }
}

class _StateRowData {
  const _StateRowData({required this.label, required this.value});
  final String label;
  final String value;
}
0
likes
160
points
6
downloads
screenshot

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Flutter widgets and utilities for parsing, validating, formatting, and displaying scientific notation such as 1.2e-3 and 1.2 × 10⁻³.

Repository (GitHub)
View/report issues

Topics

#scientific #parser #formatter #input #scientific-notation

License

MIT (license)

Dependencies

flutter

More

Packages that depend on scientific_input