**1.0.0**

This commit is contained in:
2026-01-08 14:39:34 +02:00
parent be50a0d570
commit 0d688cedc8
23 changed files with 498 additions and 369 deletions

View File

@@ -1,10 +1,5 @@
library;
export './src/border_extensions.dart';
export './src/insets_extension.dart';
export './src/list_extensions.dart';
export './src/map_extensions.dart';
export 'src/alignment_extensions.dart';
export './src/radius_extensions.dart';
export './src/string_extensions.dart';
export './src/widget_extensions.dart';
export './src/data_and_logic.dart';
export './src/layout_and_spacing.dart';
export './src/widget_wrappers.dart';

View File

@@ -1,16 +0,0 @@
import 'package:flutter/material.dart';
extension TextAlignVerticalExtension on TextAlignVertical {
Alignment toAlignment() {
switch (this) {
case TextAlignVertical.top:
return Alignment.topCenter;
case TextAlignVertical.center:
return Alignment.center;
case TextAlignVertical.bottom:
return Alignment.bottomCenter;
default:
return Alignment.center;
}
}
}

View File

@@ -1,7 +0,0 @@
import 'package:flutter/painting.dart';
extension BorderExtension on BorderDirectional {
BorderDirectional all(BorderSide side) {
return BorderDirectional(top: side, bottom: side, start: side, end: side);
}
}

View File

@@ -0,0 +1,95 @@
import 'dart:math';
import 'package:flutter/material.dart';
extension StringExtensions on String {
/// Parses a HexCode string into a [Color] object.
/// Supports formats like '#FFFFFF', 'FFFFFF', or '0xFFFFFFFF'.
Color get toColor {
if (this == '') return const Color(0xFF000000);
String hexColor = replaceAll('#', '');
hexColor = hexColor.replaceAll('0x', '');
hexColor = hexColor.padLeft(6, '0');
hexColor = hexColor.padLeft(8, 'F');
final int length = hexColor.length;
return Color(int.tryParse('0x${hexColor.substring(length - 8, length)}') ?? 0xFF000000);
}
/// Capitalizes the first letter of the string.
String get capitalize => (length > 1) ? this[0].toUpperCase() + substring(1) : toUpperCase();
/// Converts CamelCase strings to a space-separated Title Case string.
/// Example: "myVariableName" -> "My Variable Name"
String get camelCaseToTitle {
final String spaced = replaceAllMapped(
RegExp(r'([a-z])([A-Z])'),
(match) => '${match.group(1)} ${match.group(2)}',
);
return spaced.split(' ').map((word) => word[0].toUpperCase() + word.substring(1)).join(' ');
}
/// Returns [anotherString] if the current string is empty or contains only whitespace.
String replaceIfEmpty(String anotherString) => trim().isEmpty ? anotherString : this;
}
extension NullableStringExtensions on String? {
/// Returns [anotherString] if the current string is null, empty, or only whitespace.
String replaceIfEmptyOrNull(String anotherString) => (this == null || this!.trim().isEmpty) ? anotherString : this!;
}
extension ListEx<E> on List<E>? {
/// Returns true if the list contains all elements of [otherList]. Optimized $O(n)$.
bool containsAll(List<E> otherList) {
if (this == null || this!.isEmpty) return false;
if (otherList.isEmpty) return true;
final thisSet = this!.toSet();
return otherList.every(thisSet.contains);
}
/// Returns a new list containing only unique elements from the current list.
List<E> getUnique() => this?.toSet().toList() ?? <E>[];
/// Returns a list containing only non-null elements.
List<E> get nonNulls => this?.whereType<E>().toList() ?? [];
/// Safely attempts to reduce the list to a single value. Returns null if empty.
E? tryReduce(E Function(E a, E b) reducer) {
if (this == null || this!.isEmpty) return null;
final filtered = nonNulls;
return filtered.isNotEmpty ? filtered.reduce(reducer) : null;
}
/// Returns a sublist limited to the first [limit] items.
List<E>? limit(int limit) => (this?.length ?? 0) > limit ? this?.sublist(0, limit) : this;
}
extension MapListExtensions<T, B> on List<Map<T, B>> {
/// Merges a list of maps into a single map.
/// An optional [repeatingKeysValueComparer] can handle logic when keys collide.
Map<T, B> mergeAllMaps({void Function(T key, B value1, B value2)? repeatingKeysValueComparer}) {
final Map<T, B> r = <T, B>{};
for (var m in this) {
m.forEach((key, value) {
if (!r.containsKey(key)) {
r[key] = value;
} else {
repeatingKeysValueComparer?.call(key, r[key] as B, value);
}
});
}
return r;
}
}
extension IntExtensions on int {
/// Generates a random numeric pin code with a length equal to the integer value.
int get generatePinCode {
if (this <= 0) return 0;
final min = pow(10, this - 1).toInt();
final max = pow(10, this).toInt() - 1;
return Random().nextInt(max - min + 1) + min;
}
}
extension DoubleExtensions on double {
/// Clamps the double value between [min] and [max].
double clampDouble(double min, double max) => this < min ? min : (this > max ? max : this);
}

View File

@@ -1,20 +0,0 @@
import 'package:flutter/widgets.dart';
extension InsetsExtension on EdgeInsetsGeometry {
copyWith(EdgeInsetsGeometry g) => add(g);
//
EdgeInsetsDirectional resolveToDirectional(TextDirection direction) {
//
return EdgeInsetsDirectional.fromSTEB(direction == TextDirection.ltr ? resolve(direction).left : resolve(direction).right, resolve(direction).top,
direction == TextDirection.ltr ? resolve(direction).right : resolve(direction).left, resolve(direction).bottom);
//
}
}
extension InsetsNumExtension on num {
EdgeInsets get symH => EdgeInsets.symmetric(horizontal: toDouble());
EdgeInsets get symV => EdgeInsets.symmetric(vertical: toDouble());
EdgeInsets get padAll => EdgeInsets.all(toDouble());
EdgeInsets get padTop => EdgeInsets.only(top: toDouble());
EdgeInsets get padBot => EdgeInsets.only(bottom: toDouble());
}

View File

@@ -0,0 +1,61 @@
import 'package:flutter/widgets.dart';
extension InsetsExtension on EdgeInsetsGeometry {
/// Combines two [EdgeInsetsGeometry] objects by adding them together.
EdgeInsetsGeometry copyWith(EdgeInsetsGeometry g) => add(g);
/// Converts [EdgeInsetsGeometry] to [EdgeInsetsDirectional] based on [direction].
EdgeInsetsDirectional resolveToDirectional(TextDirection direction) {
final res = resolve(direction);
final isLtr = direction == TextDirection.ltr;
return EdgeInsetsDirectional.fromSTEB(
isLtr ? res.left : res.right, res.top,
isLtr ? res.right : res.left, res.bottom
);
}
}
extension InsetsNumExtension on num {
/// Returns [EdgeInsets] with symmetric horizontal spacing.
EdgeInsets get symH => EdgeInsets.symmetric(horizontal: toDouble());
/// Returns [EdgeInsets] with symmetric vertical spacing.
EdgeInsets get symV => EdgeInsets.symmetric(vertical: toDouble());
/// Returns [EdgeInsets] with uniform spacing on all sides.
EdgeInsets get padAll => EdgeInsets.all(toDouble());
/// Returns [EdgeInsets] with spacing only on the top.
EdgeInsets get padTop => EdgeInsets.only(top: toDouble());
/// Returns [EdgeInsets] with spacing only on the bottom.
EdgeInsets get padBot => EdgeInsets.only(bottom: toDouble());
/// Returns [EdgeInsetsDirectional] with spacing only at the start.
EdgeInsetsDirectional get padStart => EdgeInsetsDirectional.only(start: toDouble());
/// Returns [EdgeInsetsDirectional] with spacing only at the end.
EdgeInsetsDirectional get padEnd => EdgeInsetsDirectional.only(end: toDouble());
/// Returns a width value equal to a percentage of the screen width.
double ofSW(BuildContext c) => MediaQuery.sizeOf(c).width * toDouble() / 100;
/// Returns a height value equal to a percentage of the screen height.
double ofSH(BuildContext c) => MediaQuery.sizeOf(c).height * toDouble() / 100;
}
extension RadiusExtension on BorderRadiusGeometry {
/// Combines two [BorderRadiusGeometry] objects by adding them together.
BorderRadiusGeometry copyWith(BorderRadiusGeometry g) => add(g);
/// Resolves [BorderRadiusGeometry] to [BorderRadiusDirectional] based on [direction].
BorderRadiusDirectional resolveToDirectional(TextDirection direction) {
final res = resolve(direction);
return BorderRadiusDirectional.only(
topStart: direction == TextDirection.ltr ? res.topLeft : res.topRight,
topEnd: direction == TextDirection.rtl ? res.topLeft : res.topRight,
bottomEnd: direction == TextDirection.rtl ? res.bottomLeft : res.bottomRight,
bottomStart: direction == TextDirection.ltr ? res.bottomLeft : res.bottomRight,
);
}
}

View File

@@ -1,20 +0,0 @@
extension ListExtension<E> on List<E> {
bool containsAll(List<E> otherList) {
List<bool> checks = <bool>[];
//
for (E thisElement in otherList) {
checks.add(contains(thisElement));
}
return !checks.contains(false);
}
List<E> getUnique() {
List<E> uniqueItems = <E>[];
for (E thisElement in this) {
if (!uniqueItems.contains(thisElement)) {
uniqueItems.add(thisElement);
}
}
return uniqueItems;
}
}

View File

@@ -1,19 +0,0 @@
extension MapExtensions<B, T> on Map<B, T> {}
extension MapListExtensions<T, B> on List<Map<T, B>> {
Map<T, B> mergeAllMaps({Function(T key, B value1, B value2)? repeatingKeysValueComparer}) {
Map<T, B> r = <T, B>{};
//
for (Map<T, B> m in this) {
m.forEach((T key, B value) {
if (!r.containsKey(key)) {
r.addEntries(<MapEntry<T, B>>[MapEntry<T, B>(key, value)]);
} else {
repeatingKeysValueComparer?.call(key, r[key] as B, value);
}
});
}
//
return r;
}
}

View File

@@ -1,15 +0,0 @@
import 'package:flutter/widgets.dart';
extension RadiusExtension on BorderRadiusGeometry {
copyWith(BorderRadiusGeometry g) => add(g);
//
BorderRadiusDirectional resolveToDirectional(TextDirection direction) {
//
return BorderRadiusDirectional.only(
topStart: direction == TextDirection.ltr ? resolve(direction).topLeft : resolve(direction).topRight,
topEnd: direction == TextDirection.rtl ? resolve(direction).topLeft : resolve(direction).topRight,
bottomEnd: direction == TextDirection.rtl ? resolve(direction).bottomRight : resolve(direction).bottomRight,
bottomStart: direction == TextDirection.ltr ? resolve(direction).bottomLeft : resolve(direction).bottomRight,
);
}
}

View File

@@ -1,29 +0,0 @@
import 'package:flutter/material.dart';
extension StringExtensions on String {
/// Parse the HexCode to color
Color get toColor {
if (this == '') return const Color(0xFF000000);
String hexColor = replaceAll('#', '');
hexColor = hexColor.replaceAll('0x', '');
hexColor = hexColor.padLeft(6, '0');
hexColor = hexColor.padLeft(8, 'F');
final int length = hexColor.length;
return Color(int.tryParse('0x${hexColor.substring(length - 8, length)}') ?? 0xFF000000);
}
/// Capitalize the first letter in a string.
String get capitalize {
return (length > 1) ? this[0].toUpperCase() + substring(1) : toUpperCase();
}
/// Convert CamelCase to a capitalized space separated title.
String get camelCaseToTitle {
final String spaced = replaceAllMapped(
RegExp(r'([a-z])([A-Z])'),
(Match match) => '${match.group(1)} ${match.group(2)}',
);
return spaced.split(' ').map((String word) => word[0].toUpperCase() + word.substring(1)).join(' ');
}
}

View File

@@ -1,113 +0,0 @@
import 'dart:math' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'insets_extension.dart';
extension StringWidgetExtensions on String {
/// Convert this text into a RichText with custom quick styles and tap gestures.
RichText rich(TextStyle masterStyle, {Map<int, TextStyle>? styles, Map<int, VoidCallback>? tapCallbacks, TextAlign textAlign = TextAlign.center}) {
Map<String, TextStyle> mappedStyles = <String, TextStyle>{};
Map<String, VoidCallback> mappedCallbacks = <String, VoidCallback>{};
// Getting string pieces.
List<String> stringPieces = RegExp(r'`([^`]*)`').allMatches(this).map((RegExpMatch m) => m.group(1)).whereType<String>().toList();
// Looping on the pieces...
for (String stringPiece in stringPieces) {
String textAfter = split('`$stringPiece`')[1];
String textBeforeNext = textAfter.split('`')[0];
if (RegExp(r'{(\d+)}').hasMatch(textBeforeNext)) {
// The current piece has an index
int? itemIndex = int.tryParse(RegExp(r'{(\d+)}').allMatches(textBeforeNext).map((RegExpMatch m) => m.group(1)).whereType<String>().first);
if (itemIndex != null) {
// Styles
if (styles != null && styles.isNotEmpty && styles.keys.contains(itemIndex)) {
// Custom Style
mappedStyles.addEntries(<MapEntry<String, TextStyle>>[MapEntry<String, TextStyle>(stringPiece, styles[itemIndex]!)]);
} else {
// Master
mappedStyles.addEntries(<MapEntry<String, TextStyle>>[MapEntry<String, TextStyle>(stringPiece, masterStyle)]);
}
// Callbacks
if (tapCallbacks != null && tapCallbacks.isNotEmpty && tapCallbacks.keys.contains(itemIndex)) {
// Add tap callbak
mappedCallbacks.addEntries(<MapEntry<String, VoidCallback>>[MapEntry<String, VoidCallback>(stringPiece, tapCallbacks[itemIndex]!)]);
}
} else {
debugPrint('Something wrong with applying custom indexing in QuickRichText. $itemIndex');
}
} else {
mappedStyles.addEntries(<MapEntry<String, TextStyle>>[MapEntry<String, TextStyle>(stringPiece, masterStyle)]);
}
}
// Adding styles to the children
List<InlineSpan> children = mappedStyles.entries
.toList()
.sublist(1)
.map((MapEntry<String, TextStyle> entry) => TextSpan(
text: entry.key,
style: entry.value,
recognizer: mappedCallbacks.containsKey(entry.key) ? (TapGestureRecognizer()..onTap = mappedCallbacks[entry.key]) : null,
))
.toList();
return RichText(
text: TextSpan(
text: stringPieces[0],
style: masterStyle,
children: children,
),
textAlign: textAlign,
);
}
}
extension QuickSimpleWidgets on Widget {
/// Center the widget
Widget get center => Center(child: this);
/// Make the widget circular with RRect
Widget get circular => ClipRRect(borderRadius: BorderRadius.circular(10000000), child: this);
/// Wrap with a SizedBox
Widget sized({num? w, num? h}) => SizedBox(width: w?.toDouble(), height: h?.toDouble(), child: this);
/// Wrap with a Positioned
Widget positioned({num? bottom, num? top}) => Positioned(bottom: bottom?.toDouble(), top: top?.toDouble(), child: this);
/// Rotate the widget in quarter turns
Widget rotated({int quarterTurns = 0}) => Transform.rotate(angle: (math.pi * 22.5) * quarterTurns, child: this);
//TODO - Make it directional
/// Align
Widget align(Alignment a) => Align(alignment: a, child: this);
/// Flip the widget horizontally
Widget get flipH => Transform.flip(flipX: true, child: this);
/// Flip the widget vertically
Widget get flipV => Transform.flip(flipY: true, child: this);
/// Wrap with a SafeArea
Widget get safeArea => SafeArea(child: this);
/// Quick Opacity Adjustment
Widget opacity(num o) => Opacity(opacity: o.toDouble(), child: this);
/// Padding padAll
Widget padAll(num p) => Padding(padding: p.padAll, child: this);
/// Padding Symmetric Horizontally
Widget padSymH(num p) => Padding(padding: p.symH, child: this);
/// Padding Symmetric Vertically
Widget padSymV(num p) => Padding(padding: p.symV, child: this);
//
/// Padding Only Top
Widget padTop(num p) => Padding(padding: p.padTop, child: this);
/// Padding Only Bottom
Widget padBot(num p) => Padding(padding: p.padBot, child: this);
}

View File

@@ -0,0 +1,94 @@
import 'dart:math' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'layout_and_spacing.dart';
extension ContextExtensions on BuildContext {
/// Shortcut to get the current screen width.
double get sw => MediaQuery.sizeOf(this).width;
/// Shortcut to get the current screen height.
double get sh => MediaQuery.sizeOf(this).height;
}
extension QuickSimpleWidgets on Widget {
/// Wraps the widget in a [Center].
Widget get center => Center(child: this);
/// Clips the widget into a circular shape.
Widget get circular => ClipRRect(borderRadius: BorderRadius.circular(1e6), child: this);
/// Flips the widget horizontally.
Widget get flipH => Transform.flip(flipX: true, child: this);
/// Flips the widget vertically.
Widget get flipV => Transform.flip(flipY: true, child: this);
/// Wraps the widget in a [SafeArea].
Widget get safeArea => SafeArea(child: this);
/// Wraps the widget in a [SizedBox] with specified dimensions.
Widget sized({num? w, num? h}) => SizedBox(width: w?.toDouble(), height: h?.toDouble(), child: this);
/// Wraps the widget in a [Positioned] for use within a [Stack].
Widget positioned({num? bottom, num? top}) => Positioned(bottom: bottom?.toDouble(), top: top?.toDouble(), child: this);
/// Rotates the widget by [quarterTurns] (90 degree increments).
Widget rotated({int quarterTurns = 0}) => Transform.rotate(angle: (math.pi / 2) * quarterTurns, child: this);
/// Wraps the widget in an [Align] widget.
Widget align(Alignment a, {TextDirection? textDirection}) => Align(alignment: textDirection != null ? a.resolve(textDirection) : a, child: this);
/// Changes the [opacity] of the widget.
Widget opacity(num o) => Opacity(opacity: o.toDouble(), child: this);
/// Applies uniform padding around the widget.
Widget padAll(num p) => Padding(padding: p.padAll, child: this);
/// Applies symmetric horizontal padding.
Widget padSymH(num p) => Padding(padding: p.symH, child: this);
/// Applies symmetric vertical padding.
Widget padSymV(num p) => Padding(padding: p.symV, child: this);
/// Applies padding only to the top.
Widget padTop(num p) => Padding(padding: p.padTop, child: this);
/// Applies padding only to the bottom.
Widget padBot(num p) => Padding(padding: p.padBot, child: this);
/// Applies padding to the start (RTL friendly).
Widget padStart(num p) => Padding(padding: p.padStart, child: this);
/// Applies padding to the end (RTL friendly).
Widget padEnd(num p) => Padding(padding: p.padEnd, child: this);
/// Returns the widget if [condition] is true, otherwise returns an empty [SizedBox].
Widget visibleIf(bool condition) => condition ? this : const SizedBox.shrink();
}
extension StringWidgetExtensions on String {
/// Converts a string into a [RichText] using a custom markup: `Text`{index}.
/// [masterStyle] is the default; [styles] and [tapCallbacks] map to the bracketed index.
RichText rich(TextStyle masterStyle, {Map<int, TextStyle>? styles, Map<int, VoidCallback>? tapCallbacks, TextAlign textAlign = TextAlign.center}) {
final List<InlineSpan> spans = [];
final pattern = RegExp(r'`([^`]*)`(?:\{(\d+)\})?');
int lastMatchEnd = 0;
for (final Match match in pattern.allMatches(this)) {
if (match.start > lastMatchEnd) {
spans.add(TextSpan(text: substring(lastMatchEnd, match.start), style: masterStyle));
}
final int? index = int.tryParse(match.group(2) ?? '');
spans.add(TextSpan(
text: match.group(1) ?? '',
style: (index != null && styles?[index] != null) ? styles![index] ?? masterStyle : masterStyle,
recognizer: (index != null && tapCallbacks?[index] != null) ? (TapGestureRecognizer()..onTap = tapCallbacks![index]) : null,
));
lastMatchEnd = match.end;
}
if (lastMatchEnd < length) spans.add(TextSpan(text: substring(lastMatchEnd), style: masterStyle));
return RichText(textAlign: textAlign, text: TextSpan(children: spans));
}
}