Skip to main content

extensionresoft 2.0.0 copy "extensionresoft: ^2.0.0" to clipboard
extensionresoft: ^2.0.0 copied to clipboard

Reusable Flutter widgets, utilities and extensions: a validated text field with dropdown, secure PIN entry, range slider, rating bar, animations and more. Depends on Flutter only.

ExtensionResoft #

pub package pub points Pub Monthly Downloads likes Star on Github Awesome Flutter License: BSD

A comprehensive toolkit of extensions, widgets, and utilities to accelerate Flutter development with less code. ExtensionResoft optimizes common tasks, enhances UI components, and provides robust architecture patterns.

Features #

📝 Enhanced Text/Form Field Input #

Build sophisticated form fields with extensive customization:

  • CustomTextField: A highly customizable text input field with validation, dropdown support, and accessibility features.
  • Fixed Height Support: Unique height parameter that automatically calculates internal padding to ensure consistency with other widgets (like buttons) without manual calculation.
  • Validation System: Built-in validation with error, warning, and info severity levels.
  • Password Visibility: Configurable password visibility toggle with custom icons
  • Dropdown Support: Seamless integration with dropdown menus
  • Accessibility: Comprehensive semantics and validation announcements
  • Performance Optimized: Efficient state management and decoration caching
  • Theming Support: Automatic light/dark mode adaptation
  • Feedback Widget: Customizable validation messages with icons and animations

🎚️ Interactive UI Components #

Create modern, responsive user interfaces:

  • CustomRangeSlider: Highly customizable dual-handle range slider with:

    • Custom theming for track, thumb, and overlay colors
    • Optional haptic feedback and accessibility labels
    • Discrete or continuous value selection with validation
    • Animated thumb scaling with Material 3 inspired design
    • Elevation shadow effects and rounded track shapes
    • Value clamping and interactive feedback
  • CustomRatingBar: Advanced star rating system with:

    • Support for half-ratings and custom icons
    • Interactive drag and tap gestures
    • Smooth animations for rating changes
    • Highly customizable appearance (colors, size, spacing)
    • Optional rating text display and custom item builders

🧬 Functional Programming Support #

Write more expressive and type-safe code:

  • Either<L, R>: Comprehensive error handling with Left/Right pattern for failure/success scenarios

    • Transformation methods (map, bimap, flatMap, fold)
    • Error handling utilities (tryCatch, tryCatchAsync)
    • Combining operations (zip, zipWith) and recovery mechanisms
    • Future integration with async operation extensions
    • EitherUtils for sequence, traverse, and firstRight operations
  • Option: Elegant optional value handling with Some/None pattern

    • Core operations (fold, map, flatMap, getOrElse)
    • Conversion utilities to Either types
    • Null-safe programming patterns
  • Unit: Type-safe representation of absence of value in generic contexts

📦 Moved out in 2.0.0 #

Internet checks and network images need extra packages, so they are separate packages now and extensionresoft depends on Flutter only:

  • internet_status: real internet-access checks (DNS, socket, HTTP), a status stream and an offline banner (InternetConnectionChecker, InternetResult, ConnectionBanner, ConnectionBannerStyle).
  • any_source_image: one image widget for a network URL, an asset or a file (AppImage, AppCircleImage, ImageSourceType, ImageBackground, and the 'path'.img() and circleImage() helpers).

Upgrading from 1.x

Only code that used the classes above is affected. CustomTextField, PinEntry, CustomRangeSlider, CustomRatingBar, the animations, Either/Option and the other extensions are unchanged.

  1. Add the package or packages you need next to extensionresoft:

    dependencies:
      extensionresoft: ^2.0.0
      internet_status: ^1.0.0     # if you used the connectivity classes
      any_source_image: ^1.0.0    # if you used the image classes
    
  2. Update the imports in files that use the moved classes:

    // before
    import 'package:extensionresoft/extensionresoft.dart';
    
    // after (keep the line above too if the file uses other extensionresoft features)
    import 'package:internet_status/internet_status.dart';    // InternetConnectionChecker, InternetResult, ConnectionBanner
    import 'package:any_source_image/any_source_image.dart';  // AppImage, AppCircleImage, ImageBackground, 'path'.img()
    
  3. Run flutter pub get. The moved APIs are the same, so no other code changes are needed.

connectivity_plus, rxdart and cached_network_image are no longer pulled in by extensionresoft. If your app imports them directly, add them to your own pubspec.yaml. Staying on ^1.5.1 keeps everything as it was.

🎭 Animation Utilities #

Create smooth, performant animations with minimal code:

  • AnimatedFadeScale: Combined fade and scale animation with configurable timing and curves

    • Customizable start/end scales
    • Optional delay before animation
    • Manual animation control via value parameter
    • Material transparency support
  • FadeSlideTransition: High-performance view transitions with fade and slide effects

    • Smooth content transitions with queuing support
    • Configurable slide direction and distance
    • Key-based transition triggering
    • Optimized for rapid content changes
    • Predefined fast/slow duration presets

🔐 Secure PIN Authentication UI #

Build secure user authentication flows:

  • PinEntry Widget: Customizable PIN entry with multiple security configurations
  • Visual Customization: Style input fields and keyboard components separately
  • Validation Support: Custom handlers for completion and validation events

🧰 UI Extension Toolkit #

Write less code for common UI patterns:

  • Spacing Extensions: Clean spacer syntax with both method and getter options
  • Custom Cards: Create styled cards with simple radius-based extensions
  • Text Styling: Format text with fluent extensions for improved readability
  • List Extensions: Calculate item margins with position-based utilities returning named records

🧠 Logic & Functional Extensions #

Enhance code clarity and maintenance:

  • Conditional Functions: Widget-friendly alternatives to ternary operators
  • Path Extensions: Apply transformations to values with clean syntax
  • Value Management: Robust value retrieval with fallback handling

Getting Started #

Installation #

Add the package to your pubspec.yaml:

dependencies:
  extensionresoft: ^2.0.0

Then run:

flutter pub get

Basic Usage #

Import the package:

import 'package:extensionresoft/extensionresoft.dart';

Usage Examples #

Enhanced Text Input #

    // Password field with visibility toggle
    CustomTextField(
      margin: EdgeInsets.symmetric(vertical: 8.0),
      labelText: 'Password',
      isPassword: true,
      passwordVisibilityConfig: PasswordVisibilityConfig(
        iconSize: 22,
        visibilityOnTooltip: 'Hide password',
        visibilityOffTooltip: 'Show password',
      ),
    );

    // Dropdown field
    CustomTextField<int>(
      labelText: 'Age Group',
      items: [
        DropdownMenuItem(value: 1, child: Text('Under 18')),
        DropdownMenuItem(value: 2, child: Text('18-25')),
        DropdownMenuItem(value: 3, child: Text('26-35')),
      ],
      onDropdownChanged: (value) {
        print('Selected age group: $value');
      },
    );

    // Styled text field with custom feedback
    CustomTextField(
      labelText: 'Email',
      hintText: 'your@email.com',
      keyboardType: TextInputType.emailAddress,
      borderRadius: BorderRadius.circular(12),
      fillColor: Colors.grey[100],
      borderColor: Colors.blueGrey,
      focusColor: Colors.blue,
      errorStyle: TextStyle(color: Colors.red[800]),
      warningStyle: TextStyle(color: Colors.orange[800]),
      helperStyle: TextStyle(color: Colors.grey),
      helperText: 'Enter a valid email address',
      feedbackShowIcons: true,
      feedbackPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    );

    // Field with validation controller integration
    final validationController = ValidationController();

    // Standard height (48) matches many standard buttons
    CustomTextField(
      labelText: 'Username',
      height: 50, // Ensures consistent height and handles internal padding
      prefixIcon: Icon(Icons.person),
    );

    // Can also be used for multiple fields wrapped in a `Form`.
    CustomTextField(
      labelText: 'Order Number',
      validationController: validationController,
      validator: (value) {
        if (value == null || value.isEmpty) return 'Required field';
        if (!RegExp(r'^ORD-\d{4}$').hasMatch(value)) {
          return 'Format: ORD-1234';
        }
        return null;
      },
    );

    // Later in your code
    if (validationController.validate()) {
      // All fields valid
    }

Range Slider #

Build elegant dual-handle range selectors with Material 3 design:

    // Basic range slider with custom styling
    CustomRangeSlider(
      values: RangeValues(20, 80),
      min: 0,
      max: 100,
      onChanged: (values) {
        setState(() {
          _currentRange = values;
        });
      },
      trackHeight: 4.0,
      thumbRadius: 16.0,
      enableHaptics: true,
    );

    // Custom themed range slider
    CustomRangeSlider(
      values: RangeValues(30, 70),
      min: 0,
      max: 100,
      customTheme: CustomRangeSliderTheme(
        activeTrackColor: Colors.deepPurple,
        inactiveTrackColor: Colors.grey[300],
        thumbColor: Colors.white,
        overlayColor: Colors.deepPurple.withValues(alpha: 0.12),
        thumbBorderColor: Colors.deepPurple,
      ),
      onChanged: (values) {
        print('Range: ${values.start.round()} - ${values.end.round()}');
      },
    );

    // Discrete range slider with divisions
    CustomRangeSlider(
      values: RangeValues(25, 75),
      min: 0,
      max: 100,
      divisions: 20, // Creates 20 discrete steps
      trackHeight: 6.0,
      thumbRadius: 20.0,
      onChanged: (values) {
        _updatePriceFilter(values.start, values.end);
      },
      onChangeEnd: (values) {
        // Called when user finishes dragging
        _savePricePreferences(values);
      },
    );

    // Price range selector with custom formatting
    CustomRangeSlider(
      values: RangeValues(_minPrice, _maxPrice),
      min: 0,
      max: 1000,
      divisions: 100,
      customTheme: CustomRangeSliderTheme(
        activeTrackColor: Colors.green[600],
        thumbColor: Colors.white,
        thumbBorderColor: Colors.green[600],
        overlayColor: Colors.green.withValues(alpha: 0.1),
      ),
      onChanged: (values) {
        setState(() {
          _minPrice = values.start;
          _maxPrice = values.end;
        });
      },
      semanticFormatter: (value) => '\$${value.round()}',
      enableHaptics: true,
    );

    // Safe constructor with automatic value clamping
    CustomRangeSlider.safe(
      values: RangeValues(-10, 150), // Values outside bounds
      min: 0,
      max: 100,
      // Automatically clamps to RangeValues(0, 100)
      customTheme: CustomRangeSliderTheme(
        activeTrackColor: Colors.blue[700],
        inactiveTrackColor: Colors.blue[100],
        thumbColor: Colors.white,
        thumbBorderColor: Colors.blue[700],
      ),
      onChanged: (values) {
        // Handle validated range changes
      },
    );

    // Age range selector with custom feedback
    RangeValues _ageRange = RangeValues(18, 65);
    
    Column(
      children: [
        Text(
          'Age Range: ${_ageRange.start.round()} - ${_ageRange.end.round()} years',
          style: Theme.of(context).textTheme.titleMedium,
        ),
        16.spY, // Using ExtensionResoft spacing
        CustomRangeSlider(
          values: _ageRange,
          min: 16,
          max: 100,
          divisions: 84, // One division per year
          trackHeight: 5.0,
          thumbRadius: 18.0,
          customTheme: CustomRangeSliderTheme(
            activeTrackColor: Colors.orange[600],
            inactiveTrackColor: Colors.grey[300],
            thumbColor: Colors.white,
            overlayColor: Colors.orange.withValues(alpha: 0.15),
            thumbBorderColor: Colors.orange[600],
          ),
          onChanged: (values) {
            setState(() => _ageRange = values);
          },
          semanticFormatter: (value) => '${value.round()} years old',
        ),
      ],
    );

    // Performance monitoring range (0-100%)
    CustomRangeSlider(
      values: RangeValues(_lowThreshold, _highThreshold),
      min: 0.0,
      max: 100.0,
      divisions: 100,
      trackHeight: 3.0,
      thumbRadius: 14.0,
      customTheme: CustomRangeSliderTheme(
        activeTrackColor: _getPerformanceColor(),
        thumbColor: Colors.white,
        thumbBorderColor: _getPerformanceColor(),
        overlayColor: _getPerformanceColor().withValues(alpha: 0.1),
      ),
      onChanged: (values) {
        setState(() {
          _lowThreshold = values.start;
          _highThreshold = values.end;
        });
        _updatePerformanceThresholds(values);
      },
      semanticFormatter: (value) => '${value.round()}% performance',
      enableHaptics: false, // Disable for frequent updates
    );

    // Time range selector (hours)
    CustomRangeSlider(
      values: RangeValues(_startHour.toDouble(), _endHour.toDouble()),
      min: 0,
      max: 23,
      divisions: 23,
      trackHeight: 4.0,
      thumbRadius: 16.0,
      customTheme: CustomRangeSliderTheme(
        activeTrackColor: Colors.indigo[600],
        inactiveTrackColor: Colors.indigo[100],
        thumbColor: Colors.white,
        thumbBorderColor: Colors.indigo[600],
      ),
      onChanged: (values) {
        setState(() {
          _startHour = values.start.round();
          _endHour = values.end.round();
        });
      },
      semanticFormatter: (value) {
        final hour = value.round();
        return '${hour.toString().padLeft(2, '0')}:00';
      },
    );

Rating Bar #

Create interactive and animated rating systems:

    // Standard star rating
    CustomRatingBar(
      initialRating: 3.5,
      onRatingChanged: (rating) => print('Rating: $rating'),
    );

    // Highly customized rating bar
    CustomRatingBar(
      maxRating: 10,
      iconSize: 32,
      activeColor: Colors.deepOrange,
      inactiveColor: Colors.grey[300],
      allowHalfRating: true,
      showRatingText: true,
      activeIcon: Icons.favorite,
      inactiveIcon: Icons.favorite_border,
      onRatingChanged: (rating) => _updateUserPreference(rating),
    );

    // Read-only display
    CustomRatingBar(
      initialRating: 4.8,
      isInteractive: false,
      allowHalfRating: true,
      activeColor: Colors.amber,
    );

Functional Programming Support #

Handle errors and optional values with type-safe functional patterns:

    // Either for error handling
    Either<String, int> parseNumber(String input) {
      try {
        return Either.right(int.parse(input));
      } catch (e) {
        return Either.left('Invalid number: $input');
      }
    }
    
    // Chain operations with flatMap
    final result = parseNumber('42')
        .flatMap((n) => n > 0 ? Either.right(n * 2) : Either.left('Negative'))
        .fold((error) => 'Error: $error', (value) => 'Result: $value');
    
    // Async error handling
    final apiResult = await Either.tryCatchAsync<String, UserData>(
      (error, stackTrace) => 'API Error: ${error.toString()}',
      () => fetchUserData(userId),
    );
    
    // Option for nullable values
    Option<User> findUser(String id) {
      final user = users.firstWhereOrNull((u) => u.id == id);
      return user != null ? Option.some(user) : Option.none();
    }
    
    // Transform optional values
    final greeting = findUser('123')
        .map((user) => 'Hello, ${user.name}!')
        .getOrElse(() => 'User not found');
    
    // Convert between types
    final userEither = findUser('123')
        .toEither(() => 'User not found');

Animation Utilities #

    // Basic fade and scale animation
    AnimatedFadeScale(
      duration: Duration(milliseconds: 500),
      child: Container(
        width: 200,
        height: 200,
        color: Colors.blue,
        child: Center(child: Text('Hello!')),
      ),
    );

    // Staggered animations with delay
    Column(
      children: [
        AnimatedFadeScale(
          delay: Duration(milliseconds: 100),
          child: Card(child: /*...*/),
        ),
        AnimatedFadeScale(
          delay: Duration(milliseconds: 200),
          child: Card(child: /*...*/),
        ),
        AnimatedFadeScale(
          delay: Duration(milliseconds: 300),
          child: Card(child: /*...*/),
        ),
      ],
    );

    // Manual animation control
    double _animationValue = 0.0;

    AnimatedFadeScale(
      value: _animationValue,
      child: FloatingActionButton(
        onPressed: () {
          setState(() {
            _animationValue = _animationValue == 0.0 ? 1.0 : 0.0;
          });
        },
      ),
    );

    // FadeSlideTransition with key-based triggering
    final _transitionKey = UniqueKey();

    FadeSlideTransition(
      transitionKey: _transitionKey,
      duration: Duration(milliseconds: 400),
      initialSlideOffset: Offset(0, 0.1), // Slide up from bottom
      child: _buildContentWidget(),
    );

    // Later when content needs to change
    setState(() {
      _transitionKey = UniqueKey(); // Triggers new transition
    });

    // Using predefined durations
    FadeSlideTransition.fast(
      child: NotificationBadge(count: 5),
    );

    FadeSlideTransition.slow(
      child: HeroImage(url: imageUrl),
    );

    // Transition with completion callback
    FadeSlideTransition(
      child: ProfileHeader(user: user),
      onTransitionComplete: () {
        print('Transition finished!');
      },
    );

PIN Authentication #

    PinEntry(
      pinLength: 6,
      onInputComplete: (pin) {
        // Handle PIN validation
      },
      inputFieldConfiguration: InputFieldConfiguration(
        obscureText: true,
        fieldFillColor: Colors.grey[200],
        focusedBorderColor: Colors.blue,
      ),
      keyboardConfiguration: KeyboardConfiguration(
        keyBackgroundColor: Colors.white,
        keyTextStyle: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
      ),
    )

UI Extensions #

    // Spacing utilities
    16.spaceX()  // SizedBox(width: 16)
    24.spaceY()  // SizedBox(height: 24)
    32.spaceXY() // SizedBox(width: 32, height: 32)
    
    // Getters for even cleaner code
    20.spX  // SizedBox(width: 20)
    30.spY  // SizedBox(height: 30)
    
    // Custom card with styling
    12.radius(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Text('Rounded Card Example'),
      ),
      elevation: 2,
      color: Colors.blue[50],
      strokeColor: Colors.black12,
    )
    
    // Text styling
    'Hello Flutter'.edit(
      textStyle: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.bold,
        color: Colors.teal,
      ),
      textAlign: TextAlign.center,
    )

Logic & Functional Extensions #

    // Path extension to transform values
    final doubled = 16.p((n) => n * 2);  // 32
    
    // Widget-friendly conditional logic
    final result = condition(isActive, 'Active', 'Inactive');
    
    // Function-based conditionals (lazy evaluation)
    final message = conditionFunction(
      hasPermission,
      () => 'Access granted',
      () => 'Access denied: ${getErrorMessage()}',
    );
    
    // Safe value retrieval with fallback
    final displayName = get(user.name, 'Guest User');

Advanced Usage #

See the API Reference for comprehensive documentation of all available extensions and utilities.

Migration from 1.0.0 to 1.1.0 #

While version 1.1.0 maintains backward compatibility, we recommend the following changes:

  • Replace assetFallback with fallbackImage parameter in image widgets for improved naming consistency
  • Take advantage of the new File object support in image widgets
  • Utilize new borderRadius and backgroundColor parameters for enhanced customization

Screenshots #

(None applicable in this current release)

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/amazing-feature)
  3. Commit your Changes (git commit -m 'Add some amazing feature')
  4. Push to the Branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License #

This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.

8
likes
160
points
471
downloads

Documentation

API reference

Publisher

verified publisherkenresoft.com

Weekly Downloads

Reusable Flutter widgets, utilities and extensions: a validated text field with dropdown, secure PIN entry, range slider, rating bar, animations and more. Depends on Flutter only.

Repository (GitHub)
View/report issues

Topics

#extensions #text-field #pin-entry #widget #productivity

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on extensionresoft