**1.0.0**
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
95
lib/src/data_and_logic.dart
Normal file
95
lib/src/data_and_logic.dart
Normal 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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
61
lib/src/layout_and_spacing.dart
Normal file
61
lib/src/layout_and_spacing.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(' ');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
94
lib/src/widget_wrappers.dart
Normal file
94
lib/src/widget_wrappers.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user