From 0d688cedc85ccbb678684fbf8651397156862215 Mon Sep 17 00:00:00 2001 From: "Michael W. Aziz" Date: Thu, 8 Jan 2026 14:39:34 +0200 Subject: [PATCH] **1.0.0** --- .dart_tool/package_config.json | 27 ++++--- .dart_tool/package_graph.json | 74 +++++++++++++++++++ .dart_tool/version | 2 +- CHANGELOG.md | 9 +-- COPYRIGHT | 1 - LICENSE | 25 +++++++ README.md | 113 +++++++++++++++++++----------- analysis_options.yaml | 84 +++++++++++----------- debug.log | 2 - lib/astromic_extensions.dart | 11 +-- lib/src/alignment_extensions.dart | 16 ----- lib/src/border_extensions.dart | 7 -- lib/src/data_and_logic.dart | 95 +++++++++++++++++++++++++ lib/src/insets_extension.dart | 20 ------ lib/src/layout_and_spacing.dart | 61 ++++++++++++++++ lib/src/list_extensions.dart | 20 ------ lib/src/map_extensions.dart | 19 ----- lib/src/radius_extensions.dart | 15 ---- lib/src/string_extensions.dart | 29 -------- lib/src/widget_extensions.dart | 113 ------------------------------ lib/src/widget_wrappers.dart | 94 +++++++++++++++++++++++++ pubspec.lock | 20 +++--- pubspec.yaml | 10 +-- 23 files changed, 498 insertions(+), 369 deletions(-) create mode 100644 .dart_tool/package_graph.json delete mode 100644 COPYRIGHT create mode 100644 LICENSE delete mode 100644 debug.log delete mode 100644 lib/src/alignment_extensions.dart delete mode 100644 lib/src/border_extensions.dart create mode 100644 lib/src/data_and_logic.dart delete mode 100644 lib/src/insets_extension.dart create mode 100644 lib/src/layout_and_spacing.dart delete mode 100644 lib/src/list_extensions.dart delete mode 100644 lib/src/map_extensions.dart delete mode 100644 lib/src/radius_extensions.dart delete mode 100644 lib/src/string_extensions.dart delete mode 100644 lib/src/widget_extensions.dart create mode 100644 lib/src/widget_wrappers.dart diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index f1f7333..118b404 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -17,19 +17,19 @@ "name": "flutter", "rootUri": "file:///C:/Users/micwa/fvm/versions/stable/packages/flutter", "packageUri": "lib/", - "languageVersion": "3.7" + "languageVersion": "3.8" }, { "name": "flutter_lints", - "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-5.0.0", + "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0", "packageUri": "lib/", - "languageVersion": "3.5" + "languageVersion": "3.8" }, { "name": "lints", - "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/lints-5.1.1", + "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.0.0", "packageUri": "lib/", - "languageVersion": "3.6" + "languageVersion": "3.8" }, { "name": "material_color_utilities", @@ -39,33 +39,32 @@ }, { "name": "meta", - "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.16.0", + "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0", "packageUri": "lib/", - "languageVersion": "2.12" + "languageVersion": "3.5" }, { "name": "sky_engine", "rootUri": "file:///C:/Users/micwa/fvm/versions/stable/bin/cache/pkg/sky_engine", "packageUri": "lib/", - "languageVersion": "3.7" + "languageVersion": "3.8" }, { "name": "vector_math", - "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.1.4", + "rootUri": "file:///C:/Users/micwa/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0", "packageUri": "lib/", - "languageVersion": "2.14" + "languageVersion": "3.1" }, { "name": "astromic_extensions", "rootUri": "../", "packageUri": "lib/", - "languageVersion": "3.6" + "languageVersion": "3.10" } ], - "generated": "2025-03-30T10:13:12.420517Z", "generator": "pub", - "generatorVersion": "3.7.0", + "generatorVersion": "3.10.1", "flutterRoot": "file:///C:/Users/micwa/fvm/versions/stable", - "flutterVersion": "3.29.0", + "flutterVersion": "3.38.3", "pubCache": "file:///C:/Users/micwa/AppData/Local/Pub/Cache" } diff --git a/.dart_tool/package_graph.json b/.dart_tool/package_graph.json new file mode 100644 index 0000000..e48386f --- /dev/null +++ b/.dart_tool/package_graph.json @@ -0,0 +1,74 @@ +{ + "roots": [ + "astromic_extensions" + ], + "packages": [ + { + "name": "astromic_extensions", + "version": "1.0.0", + "dependencies": [ + "flutter" + ], + "devDependencies": [ + "flutter_lints" + ] + }, + { + "name": "flutter_lints", + "version": "6.0.0", + "dependencies": [ + "lints" + ] + }, + { + "name": "flutter", + "version": "0.0.0", + "dependencies": [ + "characters", + "collection", + "material_color_utilities", + "meta", + "sky_engine", + "vector_math" + ] + }, + { + "name": "lints", + "version": "6.0.0", + "dependencies": [] + }, + { + "name": "sky_engine", + "version": "0.0.0", + "dependencies": [] + }, + { + "name": "vector_math", + "version": "2.2.0", + "dependencies": [] + }, + { + "name": "meta", + "version": "1.17.0", + "dependencies": [] + }, + { + "name": "material_color_utilities", + "version": "0.11.1", + "dependencies": [ + "collection" + ] + }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "characters", + "version": "1.4.0", + "dependencies": [] + } + ], + "configVersion": 1 +} \ No newline at end of file diff --git a/.dart_tool/version b/.dart_tool/version index ec131a8..fd30af8 100644 --- a/.dart_tool/version +++ b/.dart_tool/version @@ -1 +1 @@ -3.29.0 \ No newline at end of file +3.38.3 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9539a2d..52c93ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,3 @@ -## 0.1.2 -- **FEAT** Added Quick widget extensions. +# 1.0.0 -## 0.1.1 -- **FEAT** Added Map Extensions file and `MergeAllMaps` function. - -## 0.1.0 -- **INIT**: Initial commit in the new repo. +* **INIT**: Initial commit in the new repo. diff --git a/COPYRIGHT b/COPYRIGHT deleted file mode 100644 index 0e86da1..0000000 --- a/COPYRIGHT +++ /dev/null @@ -1 +0,0 @@ -This repository is Developed, Maintained, and is property of Michael W. Aziz (Micazi). \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..467a5f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +# License Agreement + +Copyright (c) 2026 Michael W. Aziz (Micazi). All rights reserved. + +Proprietary and Confidential. + +1. PERMITTED USE +This software is the exclusive property of Michael W. Aziz. Use of this +software is strictly limited to: + (a) Personal projects developed and maintained by Michael W. Aziz. + (b) Professional projects or client applications where Michael W. Aziz + is a direct contributor or the primary developer. + +2. RESTRICTIONS +Unauthorized copying, modification, or distribution of this software +outside of the permitted uses defined in Section 1 is strictly prohibited. +Clients are granted a license to use the compiled output of this software +within their specific project but do not acquire ownership of the +underlying source code or the right to redistribute it as a standalone package. + +3. WARRANTY & LIABILITY +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES, +OR OTHER LIABILITY, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE +SOFTWARE OR THE USE THEREOF. diff --git a/README.md b/README.md index affed55..eee0e9f 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,77 @@ -## Atromic Extensions +# Astromic Extensions -The extensions module of the **Astromic** Presentation System -Developed, Maintained, and is property of Michael W. Aziz (Micazi) +**The Foundation of the Astromic Presentation System.** -### Content +A collection of high-performance Dart extensions designed to eliminate UI boilerplate and simplify RTL (Right-to-Left) layout logic. -- on _BorderDirectional_ - - **all()** : Generate a uniform border with one borderSide. ☑️ -- on _EdgeInsetsGeometry_ - - **copyWith()** ☑️ - - **resolveToDirectional()** : Automatically reverse the EdgeInsets based on the dynamic *TextDirection*. ☑️ -- on _List_ - - **containsAll()** : Checker to see if a list contains all of the other list. ☑️ - - **getUnique()** : Get unique items only of a supplied list. ☑️ -- on _List>_ - - **mergeAllMaps()** : Merges a list of maps into one with a conditional functionality to approve the merge. ☑️ -- on _TextAlignVertical_ - - **toAlignment()** : convert a `TextAlignVertical` to an `Alignment` object. ☑️ -- on _BorderRadiusGeometry_ - - **copyWith()** ☑️ - - **resolveToDirectional()** : Automatically reverse the BorderRadius based on the dynamic *TextDirection*. ☑️ -- on _String_ - - **toColor()** : Parse a HexCode `String` into a `Color` object. ☑️ - - **capitalize()** : Capitalize the word. ☑️ - - **rich()** : Convert the string to a RichText Widget. ☑️ -- on _Widget_ - - **center** - - **circular** - - **align** - - **positioned** - - **sized** - - **rotated** - - **flipH** - - **flipV** - - **safeArea** - - **opacity** - - **padAll** - - **padSymH** - - **padSymV** - - **padTop** - - **padBot** +--- + +## 🛠 Features & API Reference + +### Layout & Spacing + +* **.resolveToDirectional()** (`EdgeInsetsGeometry`): Automatically flips insets based on the current `TextDirection`. + +* **.resolveToDirectional()** (`BorderRadiusGeometry`): Reverses corner radii (e.g., top-left to top-right) for RTL support. + +* **.copyWith()** (`EdgeInsetsGeometry`): An additive copy method for combining geometries. + +* **.padAll / .symH / .symV** (`num`): Quick-access padding generators directly from numbers. + +### Widget Wrappers (Fluent UI) + +* **Layout**: `.center()`, `.align()`, `.positioned()`, `.sized()`, `.rotated()` + +* **Transform**: `.flipH()`, `.flipV()`, `.opacity()` + +* **Spacing**: `.padAll()`, `.padSymH()`, `.padSymV()`, `.padTop()`, `.padBot()` + +* **Utility**: `.safeArea()`, `.circular()` + +### Data & Styling Utilities + +* **containsAll()** (`List`): Optimized $O(n)$ check to see if a list contains all elements of another. + +* **getUnique()** (`List`): Returns a list with duplicates removed using high-performance Set hashing. + +* **mergeAllMaps()** (`List`): Performs a conditional deep merge of map collections. + +* **.toColor()** (`String`): Parses Hex strings into Flutter `Color` objects. + +* **.capitalize()** (`String`): Standardizes string casing. + +* **.rich()** (`String`): Shorthand to convert a string directly into a `RichText` widget. + +* **.toAlignment()** (`TextAlignVertical`): Converts vertical text alignment to an `Alignment` object. + +--- + +## 📦 Installation + +```yaml +dependencies: + astromic_extensions: + git: + url: git.micazi.dev/micazi/astromic_extensions.git + ref: main +``` + +--- + +## 🤝 Client Handoff & Continuity + +To ensure project stability following the engagement, clients should follow these steps to secure a local version of this library: + + 1. Mirror Repository: Clone this repository and re-upload it to your internal Git organization. + + 2. Update Source: Update the pubspec.yaml in your application to point to your internal Git URL. + + 3. Versioning: Always use a specific Git ref (tag or commit hash) to ensure build reproducibility. + +Note: Access to the Micazi master repository is strictly controlled. Unauthorized redistribution of this source code is prohibited under the included LICENSE terms. + +--- + +## ⚖️ License + +Developed, Maintained, and property of Michael W. Aziz (Micazi). Refer to the [LICENSE](LICENSE) file for permitted use in client projects. diff --git a/analysis_options.yaml b/analysis_options.yaml index 0de3c75..c3e7c5c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,50 +1,48 @@ -include: package:lints/recommended.yaml +include: package:flutter_lints/flutter.yaml + +analyzer: + language: + # Ensures the compiler never implicitly assumes 'dynamic' when it cannot infer a type. + strict-inference: true + # Disallows the use of generic types without explicit type arguments (e.g., List instead of List). + strict-raw-types: true + errors: + # Elevates missing required parameters from a warning to a hard compile error. + missing_required_param: error + # Elevates missing return statements in non-void functions to a hard compile error. + missing_return: error linter: rules: - # ==== Project Organization Rules ==== - # Enforces relative imports to maintain project structure and avoid unnecessary long paths + # Requires explicit return types for all functions and methods to ensure API clarity. + - always_declare_return_types + # Discourages redundant type annotations on closure parameters where inference is sufficient. + - avoid_types_on_closure_parameters + # Requires the @override annotation when a class member overrides a member from a superclass. + - annotate_overrides + # Enforces the use of relative imports for files within the same package to maintain portability. - prefer_relative_imports - - # Ensures that files are named in lowercase_with_underscores format - - file_names - - # ==== Best Practices ==== - # Enforces the closing of streams and sinks to avoid memory leaks - - close_sinks - - # Avoids empty 'else' blocks to reduce confusion and improve code readability - - avoid_empty_else - - # Prefer using 'const' constructors wherever possible for better performance and immutability + # Enables the use of the 'super.parameter' syntax in constructors to reduce boilerplate. + - use_super_parameters + # Encourages 'const' constructors for classes to optimize memory and performance. - prefer_const_constructors - - # Avoid leading underscores for local variable names to prevent conflicts and improve clarity - - no_leading_underscores_for_local_identifiers - - # ==== Code Consistency ==== - # Avoids the use of 'print' statements in production code, encouraging proper logging instead - - avoid_print - - # Encourages using 'final' for fields that are not reassigned to promote immutability + # Requires 'const' for variable declarations that are initialized with a constant value. + - prefer_const_declarations + # Encourages declaring local variables as 'final' if they are not reassigned. + - prefer_final_locals + # Encourages declaring private fields as 'final' if they are not reassigned. - prefer_final_fields - - # Ensures that all types are explicitly specified for better readability and type safety - - always_specify_types - - # Ensures constructors are at the top of the class for better readability and consistency - - sort_constructors_first - - # Avoids redundant default argument values to keep the code clean - - avoid_redundant_argument_values - - # Enforces consistency by preferring single quotes over double quotes for string literals + # Triggers a warning when a future is not awaited or explicitly handled, preventing race conditions. + - unawaited_futures + # Prevents accessing BuildContext across asynchronous gaps to avoid runtime crashes. + - use_build_context_synchronously + # Ensures that Sink and StreamController instances are properly closed to prevent memory leaks. + - close_sinks + # Prevents the use of control flow statements (like return or throw) inside a finally block. + - control_flow_in_finally + # Standardizes string literals to use single quotes unless the string contains a single quote. - prefer_single_quotes - - # ==== Documentation Rules ==== - # Enforces documentation for all public classes, methods, and fields to improve API clarity - # - public_member_api_docs - - # ==== Null Safety ==== - # Avoids unnecessary null checks and encourages the use of null-aware operators - - unnecessary_null_checks + # Requires constructors to be placed before other members in a class. + - sort_constructors_first + # Disallows leading underscores for local variables to distinguish them from private class members. + - no_leading_underscores_for_local_identifiers \ No newline at end of file diff --git a/debug.log b/debug.log deleted file mode 100644 index 8b390c5..0000000 --- a/debug.log +++ /dev/null @@ -1,2 +0,0 @@ -[0330/121753.404:ERROR:crashpad_client_win.cc(811)] not connected -[0330/121753.573:ERROR:crashpad_client_win.cc(811)] not connected diff --git a/lib/astromic_extensions.dart b/lib/astromic_extensions.dart index 1ca0327..6a20b90 100644 --- a/lib/astromic_extensions.dart +++ b/lib/astromic_extensions.dart @@ -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'; \ No newline at end of file +export './src/data_and_logic.dart'; +export './src/layout_and_spacing.dart'; +export './src/widget_wrappers.dart'; diff --git a/lib/src/alignment_extensions.dart b/lib/src/alignment_extensions.dart deleted file mode 100644 index 6e5e861..0000000 --- a/lib/src/alignment_extensions.dart +++ /dev/null @@ -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; - } - } -} diff --git a/lib/src/border_extensions.dart b/lib/src/border_extensions.dart deleted file mode 100644 index 81f3f4c..0000000 --- a/lib/src/border_extensions.dart +++ /dev/null @@ -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); - } -} diff --git a/lib/src/data_and_logic.dart b/lib/src/data_and_logic.dart new file mode 100644 index 0000000..bb6e6c9 --- /dev/null +++ b/lib/src/data_and_logic.dart @@ -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 on List? { + /// Returns true if the list contains all elements of [otherList]. Optimized $O(n)$. + bool containsAll(List 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 getUnique() => this?.toSet().toList() ?? []; + + /// Returns a list containing only non-null elements. + List get nonNulls => this?.whereType().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? limit(int limit) => (this?.length ?? 0) > limit ? this?.sublist(0, limit) : this; +} + +extension MapListExtensions on List> { + /// Merges a list of maps into a single map. + /// An optional [repeatingKeysValueComparer] can handle logic when keys collide. + Map mergeAllMaps({void Function(T key, B value1, B value2)? repeatingKeysValueComparer}) { + final Map r = {}; + 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); +} \ No newline at end of file diff --git a/lib/src/insets_extension.dart b/lib/src/insets_extension.dart deleted file mode 100644 index 178bf41..0000000 --- a/lib/src/insets_extension.dart +++ /dev/null @@ -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()); -} diff --git a/lib/src/layout_and_spacing.dart b/lib/src/layout_and_spacing.dart new file mode 100644 index 0000000..a3df21f --- /dev/null +++ b/lib/src/layout_and_spacing.dart @@ -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, + ); + } +} \ No newline at end of file diff --git a/lib/src/list_extensions.dart b/lib/src/list_extensions.dart deleted file mode 100644 index 020273a..0000000 --- a/lib/src/list_extensions.dart +++ /dev/null @@ -1,20 +0,0 @@ -extension ListExtension on List { - bool containsAll(List otherList) { - List checks = []; - // - for (E thisElement in otherList) { - checks.add(contains(thisElement)); - } - return !checks.contains(false); - } - - List getUnique() { - List uniqueItems = []; - for (E thisElement in this) { - if (!uniqueItems.contains(thisElement)) { - uniqueItems.add(thisElement); - } - } - return uniqueItems; - } -} diff --git a/lib/src/map_extensions.dart b/lib/src/map_extensions.dart deleted file mode 100644 index 929f348..0000000 --- a/lib/src/map_extensions.dart +++ /dev/null @@ -1,19 +0,0 @@ -extension MapExtensions on Map {} - -extension MapListExtensions on List> { - Map mergeAllMaps({Function(T key, B value1, B value2)? repeatingKeysValueComparer}) { - Map r = {}; - // - for (Map m in this) { - m.forEach((T key, B value) { - if (!r.containsKey(key)) { - r.addEntries(>[MapEntry(key, value)]); - } else { - repeatingKeysValueComparer?.call(key, r[key] as B, value); - } - }); - } - // - return r; - } -} diff --git a/lib/src/radius_extensions.dart b/lib/src/radius_extensions.dart deleted file mode 100644 index 0563f5a..0000000 --- a/lib/src/radius_extensions.dart +++ /dev/null @@ -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, - ); - } -} diff --git a/lib/src/string_extensions.dart b/lib/src/string_extensions.dart deleted file mode 100644 index 4e6127f..0000000 --- a/lib/src/string_extensions.dart +++ /dev/null @@ -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(' '); - } -} diff --git a/lib/src/widget_extensions.dart b/lib/src/widget_extensions.dart deleted file mode 100644 index 9f05b89..0000000 --- a/lib/src/widget_extensions.dart +++ /dev/null @@ -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? styles, Map? tapCallbacks, TextAlign textAlign = TextAlign.center}) { - Map mappedStyles = {}; - Map mappedCallbacks = {}; - - // Getting string pieces. - List stringPieces = RegExp(r'`([^`]*)`').allMatches(this).map((RegExpMatch m) => m.group(1)).whereType().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().first); - if (itemIndex != null) { - // Styles - if (styles != null && styles.isNotEmpty && styles.keys.contains(itemIndex)) { - // Custom Style - mappedStyles.addEntries(>[MapEntry(stringPiece, styles[itemIndex]!)]); - } else { - // Master - mappedStyles.addEntries(>[MapEntry(stringPiece, masterStyle)]); - } - - // Callbacks - if (tapCallbacks != null && tapCallbacks.isNotEmpty && tapCallbacks.keys.contains(itemIndex)) { - // Add tap callbak - mappedCallbacks.addEntries(>[MapEntry(stringPiece, tapCallbacks[itemIndex]!)]); - } - } else { - debugPrint('Something wrong with applying custom indexing in QuickRichText. $itemIndex'); - } - } else { - mappedStyles.addEntries(>[MapEntry(stringPiece, masterStyle)]); - } - } - // Adding styles to the children - List children = mappedStyles.entries - .toList() - .sublist(1) - .map((MapEntry 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); -} diff --git a/lib/src/widget_wrappers.dart b/lib/src/widget_wrappers.dart new file mode 100644 index 0000000..27d015d --- /dev/null +++ b/lib/src/widget_wrappers.dart @@ -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? styles, Map? tapCallbacks, TextAlign textAlign = TextAlign.center}) { + final List 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)); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 3e90369..5353ece 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -26,18 +26,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" lints: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" material_color_utilities: dependency: transitive description: @@ -50,10 +50,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" sky_engine: dependency: transitive description: flutter @@ -63,10 +63,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">3.27.0" + dart: ">=3.10.0 <4.0.0" + flutter: ">3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index be3c48d..4700894 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,14 @@ name: astromic_extensions -description: The extensions module of the Astromic Presentation System. +description: A collection of high-performance Dart extensions designed to eliminate UI boilerplate and simplify RTL (Right-to-Left) layout logic. publish_to: "none" -version: 0.1.2 +version: 1.0.0 environment: - sdk: ">=3.6.0" - flutter: ">3.27.0" + sdk: ">=3.10.0" + flutter: ">3.38.0" dev_dependencies: - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 dependencies: flutter: