연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
ExtensionResoft
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:
🧬 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
🌐 Internet Connectivity Management
Detect and respond to network changes reliably:
Real-time Monitoring : Observe detailed connectivity state changes through streams
Granular Control : Monitor specific connectivity aspects with dedicated streams
Simplified API : Check network status with straightforward methods
Resilient Applications : Build offline-ready features with minimal effort
🖼️ Advanced Image Handling
Create robust image components with minimal effort:
Multi-source Support : Handle network URLs, asset paths, and file objects through a unified API
AppImage : Rectangular images with custom border radius, intelligent error handling, and fallbacks
AppCircleImage : Circular avatar images with placeholder and error states
ImageBackground : Display images with optional overlay content for enhanced flexibility
Performance Optimized : Device pixel ratio-aware caching for memory efficiency
Decoration Support : Use as BoxDecoration background images easily
🎭 Animation Utilities
Create smooth, performant animations with minimal code:
🔐 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
Image Path Extensions : Convert asset paths to image widgets directly
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: ^1.5.1
Then run:
flutter pub get
Basic Usage
Import the package:
import 'package:extensionresoft/extensionresoft.dart' ;
Usage Examples
Enhanced Text Input
CustomTextField (
margin: EdgeInsets .symmetric (vertical: 8.0 ),
labelText: 'Password' ,
isPassword: true ,
passwordVisibilityConfig: PasswordVisibilityConfig (
iconSize: 22 ,
visibilityOnTooltip: 'Hide password' ,
visibilityOffTooltip: 'Show password' ,
),
);
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 ' );
},
);
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 ),
);
final validationController = ValidationController ();
CustomTextField (
labelText: 'Username' ,
height: 50 ,
prefixIcon: Icon (Icons .person),
);
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 ;
},
);
if (validationController.validate ()) {
}
Range Slider
Build elegant dual-handle range selectors with Material 3 design:
CustomRangeSlider (
values: RangeValues (20 , 80 ),
min: 0 ,
max: 100 ,
onChanged: (values) {
setState (() {
_currentRange = values;
});
},
trackHeight: 4.0 ,
thumbRadius: 16.0 ,
enableHaptics: true ,
);
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()} ' );
},
);
CustomRangeSlider (
values: RangeValues (25 , 75 ),
min: 0 ,
max: 100 ,
divisions: 20 ,
trackHeight: 6.0 ,
thumbRadius: 20.0 ,
onChanged: (values) {
_updatePriceFilter (values.start, values.end);
},
onChangeEnd: (values) {
_savePricePreferences (values);
},
);
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 ,
);
CustomRangeSlider .safe (
values: RangeValues (-10 , 150 ),
min: 0 ,
max: 100 ,
customTheme: CustomRangeSliderTheme (
activeTrackColor: Colors .blue[700 ],
inactiveTrackColor: Colors .blue[100 ],
thumbColor: Colors .white,
thumbBorderColor: Colors .blue[700 ],
),
onChanged: (values) {
},
);
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,
CustomRangeSlider (
values: _ageRange,
min: 16 ,
max: 100 ,
divisions: 84 ,
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' ,
),
],
);
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 ,
);
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:
CustomRatingBar (
initialRating: 3.5 ,
onRatingChanged: (rating) => print ('Rating: $rating ' ),
);
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),
);
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 <String , int > parseNumber (String input) {
try {
return Either .right (int .parse (input));
} catch (e) {
return Either .left ('Invalid number: $input ' );
}
}
final result = parseNumber ('42' )
.flatMap ((n) => n > 0 ? Either .right (n * 2 ) : Either .left ('Negative' ))
.fold ((error) => 'Error: $error ' , (value) => 'Result: $value ' );
final apiResult = await Either .tryCatchAsync<String , UserData >(
(error, stackTrace) => 'API Error: ${error.toString()} ' ,
() => fetchUserData (userId),
);
Option <User > findUser (String id) {
final user = users.firstWhereOrNull ((u) => u.id == id);
return user != null ? Option .some (user) : Option .none ();
}
final greeting = findUser ('123' )
.map ((user) => 'Hello, ${user.name} !' )
.getOrElse (() => 'User not found' );
final userEither = findUser ('123' )
.toEither (() => 'User not found' );
Internet Connectivity
final connectionChecker = InternetConnectionChecker ();
final isConnected = await connectionChecker.isInternetConnected;
print ('Internet connected: $isConnected ' );
connectionChecker.onInternetConnectivityChanged.listen ((result) {
print ('Connection Status: ${result.hasInternetAccess} ' );
print ('Connection Type: ${result.connectionType} ' );
if (result.hasInternetAccess) {
} else {
}
});
connectionChecker.onIsInternetConnected.listen ((isConnected) {
print ('Internet status changed: $isConnected ' );
});
Image Handling
AppImage (
'https://example.com/profile.jpg' ,
width: 150 ,
height: 150 ,
fit: BoxFit .cover,
borderRadius: BorderRadius .circular (8 ),
backgroundColor: Colors .grey[200 ],
errorWidget: Icon (Icons .broken_image),
fallbackImage: 'assets/default_image.png' ,
)
AppCircleImage (
'assets/profile_photo.jpg' ,
radius: 40 ,
fit: BoxFit .cover,
placeholder: CircularProgressIndicator (strokeWidth: 2 ),
errorWidget: Icon (Icons .person),
)
Container (
decoration: BoxDecoration (
image: AppImage ('https://example.com/bg.jpg' )
.toDecorationImage (
decorationFit: BoxFit .cover,
fallbackImage: 'assets/default_bg.png' ,
),
),
)
'assets/image.png' .img (width: 100 , height: 100 )
'assets/avatar.png' .circleImage (fit: BoxFit .cover)
Animation Utilities
AnimatedFadeScale (
duration: Duration (milliseconds: 500 ),
child: Container (
width: 200 ,
height: 200 ,
color: Colors .blue,
child: Center (child: Text ('Hello!' )),
),
);
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: ),
),
],
);
double _animationValue = 0.0 ;
AnimatedFadeScale (
value: _animationValue,
child: FloatingActionButton (
onPressed: () {
setState (() {
_animationValue = _animationValue == 0.0 ? 1.0 : 0.0 ;
});
},
),
);
final _transitionKey = UniqueKey ();
FadeSlideTransition (
transitionKey: _transitionKey,
duration: Duration (milliseconds: 400 ),
initialSlideOffset: Offset (0 , 0.1 ),
child: _buildContentWidget (),
);
setState (() {
_transitionKey = UniqueKey ();
});
FadeSlideTransition .fast (
child: NotificationBadge (count: 5 ),
);
FadeSlideTransition .slow (
child: HeroImage (url: imageUrl),
);
FadeSlideTransition (
child: ProfileHeader (user: user),
onTransitionComplete: () {
print ('Transition finished!' );
},
);
PIN Authentication
PinEntry (
pinLength: 6 ,
onInputComplete: (pin) {
},
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
16 .spaceX ()
24 .spaceY ()
32 .spaceXY ()
20 .spX
30 .spY
12 .radius (
child: Padding (
padding: EdgeInsets .all (16 ),
child: Text ('Rounded Card Example' ),
),
elevation: 2 ,
color: Colors .blue[50 ],
strokeColor: Colors .black12,
)
'Hello Flutter' .edit (
textStyle: TextStyle (
fontSize: 20 ,
fontWeight: FontWeight .bold,
color: Colors .teal,
),
textAlign: TextAlign .center,
)
Logic & Functional Extensions
final doubled = 16 .p ((n) => n * 2 );
final result = condition (isActive, 'Active' , 'Inactive' );
final message = conditionFunction (
hasPermission,
() => 'Access granted' ,
() => 'Access denied: ${getErrorMessage()} ' ,
);
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.
Fork the Project
Create your Feature Branch (git checkout -b feature/amazing-feature)
Commit your Changes (git commit -m 'Add some amazing feature')
Push to the Branch (git push origin feature/amazing-feature)
Open a Pull Request
License
This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.