diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 441c8ff..73798bf 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.1\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_darwin-2.4.1+1\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"path_provider_android","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.15\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_android-2.4.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.1\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\sqflite_darwin-2.4.1+1\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"C:\\\\Users\\\\micwa\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[]},"dependencyGraph":[{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]}],"date_created":"2025-03-19 12:37:24.992698","version":"3.29.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider_foundation","path":"/Users/micazi/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/micazi/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"path_provider_android","path":"/Users/micazi/.pub-cache/hosted/pub.dev/path_provider_android-2.2.17/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_android","path":"/Users/micazi/.pub-cache/hosted/pub.dev/sqflite_android-2.4.1/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"path_provider_foundation","path":"/Users/micazi/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sqflite_darwin","path":"/Users/micazi/.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.2/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/micazi/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/micazi/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[]},"dependencyGraph":[{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":["sqflite_android","sqflite_darwin"]},{"name":"sqflite_android","dependencies":[]},{"name":"sqflite_darwin","dependencies":[]}],"date_created":"2025-09-12 18:26:52.817927","version":"3.32.4","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/lib/src/Widgets/src/image.widget.dart b/lib/src/Widgets/src/image.widget.dart index 00eb54d..d49b87b 100644 --- a/lib/src/Widgets/src/image.widget.dart +++ b/lib/src/Widgets/src/image.widget.dart @@ -1,8 +1,8 @@ //s1 Imports //s2 Core Package Imports +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'dart:convert'; -import 'dart:typed_data'; //s2 1st-party Package Imports //s2 3rd-party Package Imports import 'package:cached_network_image/cached_network_image.dart'; @@ -34,11 +34,10 @@ _DeclaredAssetType _parseAssetType( Uint8List? bytes, String? fallback, ) { - bool fromPath = (path != null && path != ''); - bool fromBytes = (bytes != null && bytes.isNotEmpty); - bool fromNetwork = (url != null && url.isNotEmpty && Uri.tryParse(url)?.hasAbsolutePath == true && (url.startsWith('http://') || url.startsWith('https://'))); + final bool fromPath = (path != null && path.isNotEmpty); + final bool fromBytes = (bytes != null && bytes.isNotEmpty); + final bool fromNetwork = (url != null && url.isNotEmpty && Uri.tryParse(url)?.hasAbsolutePath == true && (url.startsWith('http://') || url.startsWith('https://'))); - // return fromPath ? _DeclaredAssetType.path : fromBytes @@ -49,8 +48,6 @@ _DeclaredAssetType _parseAssetType( } class AstromicImage extends StatelessWidget { - //!SECTION - // AstromicImage({ super.key, // @@ -79,6 +76,7 @@ class AstromicImage extends StatelessWidget { this.blendMode, this.fadeInCurve, this.fadeInDuration, + this.imageQuality = 0.35, // this.svgColor, // @@ -103,13 +101,14 @@ class AstromicImage extends StatelessWidget { (sizingMaster == ImageSizingMaster.w && (widthSizing != null || fixedWidth != null)) || (sizingMaster == ImageSizingMaster.h && (heightSizing != null || fixedHeight != null)), 'Please provide the correct sizing configurations based on the SizingMaster chosen.', ); - //SECTION - Widget Arguments - //S1 -- Assets + + // SECTION - Widget Arguments + // S1 -- Assets final String? assetPath; final String? assetURL; final Uint8List? assetBytes; final String? assetFallback; - //S1 -- Sizing + // S1 -- Sizing final ImageSizingMaster? sizingMaster; final (double factor, double? min, double? max)? widthSizing; @@ -119,7 +118,7 @@ class AstromicImage extends StatelessWidget { /// Used when the height is Master and want to set fixed height OR if width is Master and want to constraint the height final double? fixedHeight; - //S1 -- STYLING + // S1 -- STYLING final bool? isCircular; final double? borderWidth; final Color? borderColor; @@ -128,81 +127,85 @@ class AstromicImage extends StatelessWidget { final List? shadow; final Color? overlayColor; final Gradient? overlayGradient; - //S1 -- CONFIGURATIONS + // S1 -- CONFIGURATIONS final Alignment? alignment; final BoxFit? fit; final BlendMode? blendMode; final Curve? fadeInCurve; final Duration? fadeInDuration; - //S1 -- SVG FILTERS + final double? imageQuality; // 0.0 = very blurry, 1.0 = full quality + // S1 -- SVG FILTERS final Color? svgColor; - //S1 -- STATE WIDGETS + // S1 -- STATE WIDGETS final Widget Function(int? loadedBytes, int? totalBytesToLoad)? loadingWidget; final Widget Function(dynamic error, StackTrace? stackTrace)? errorWidget; @override Widget build(BuildContext context) { - //SECTION - Build Setup - //s1 -Values - _DeclaredAssetType assetType = _parseAssetType(assetPath, assetURL, assetBytes, assetFallback); - dynamic assetRef = _getAssetRef(assetType); - bool isSVG = _isSVG(assetType, assetRef); - //s1 -Values - // - //s1 -Widgets + // SECTION - Build Setup + final _DeclaredAssetType assetType = _parseAssetType(assetPath, assetURL, assetBytes, assetFallback); + final dynamic assetRef = _getAssetRef(assetType); + final bool isSVG = _isSVG(assetType, assetRef); + // Default Loading Widget - Widget defaultLoadingWidget = const Text('Loading...'); + final Widget defaultLoadingWidget = const Text('Loading...'); // Default Error Widget Widget defaultErrorWidget(dynamic error) => Text('An error has happened: $error'); - // Get final svg widget - Widget? finalSVGWidget(Size size, Widget? loadingWidget) => assetType == _DeclaredAssetType.path - ? SvgPicture.asset( - assetRef, - key: ValueKey(assetRef), + // final svg widget (SVGs already handle width/height) + Widget? finalSVGWidget(Size size, Widget? loadingWidget) { + if (assetType == _DeclaredAssetType.path && assetRef is String) { + return SvgPicture.asset( + assetRef, + key: ValueKey(assetRef), + width: size.width, + height: size.height, + fit: fit!, + placeholderBuilder: (_) => SizedBox( width: size.width, height: size.height, - fit: fit!, - placeholderBuilder: (_) => SizedBox( - width: size.width, - height: size.height, - child: loadingWidget, - ), - alignment: alignment!, - colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null, - ) - : assetType == _DeclaredAssetType.url - ? SvgPicture.network( - assetRef, - key: ValueKey(assetRef), - width: size.width, - height: size.height, - fit: fit!, - placeholderBuilder: (_) => SizedBox( - width: size.width, - height: size.height, - child: loadingWidget, - ), - alignment: alignment!, - colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null, - ) - : null; + child: loadingWidget, + ), + alignment: alignment!, + colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null, + ); + } else if (assetType == _DeclaredAssetType.url && assetRef is String) { + return SvgPicture.network( + assetRef, + key: ValueKey(assetRef), + width: size.width, + height: size.height, + fit: fit!, + placeholderBuilder: (_) => SizedBox( + width: size.width, + height: size.height, + child: loadingWidget, + ), + alignment: alignment!, + colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null, + ); + } + return null; + } Widget buildImage(Size size) { + // choose filter quality from imageQuality for paint-time interpolation + final FilterQuality filterQuality = _filterQualityFromImageQuality(imageQuality ?? 1.0); + return Stack( children: [ OctoImage( - key: ValueKey(assetType == _DeclaredAssetType.bytes ? (assetRef as Uint8List).length.toString() : assetRef), - // + key: ValueKey( + assetType == _DeclaredAssetType.bytes ? 'bytes-${(assetRef as Uint8List).lengthInBytes}' : (assetRef?.toString() ?? 'unknown'), + ), width: size.width, height: size.height == double.infinity ? null : size.height, fit: fit, alignment: alignment, - filterQuality: FilterQuality.none, + filterQuality: filterQuality, color: svgColor, colorBlendMode: blendMode ?? (isSVG ? BlendMode.srcATop : null), - // errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { return errorWidget != null ? errorWidget!(error, stackTrace) @@ -210,13 +213,11 @@ class AstromicImage extends StatelessWidget { ? Image.asset(assetFallback!) : defaultErrorWidget(error); }, - // progressIndicatorBuilder: (_, ImageChunkEvent? bytes) => SizedBox( width: size.width, height: size.height == double.infinity ? null : size.height, child: loadingWidget != null ? loadingWidget!(bytes?.cumulativeBytesLoaded, bytes?.expectedTotalBytes) : defaultLoadingWidget, ), - // placeholderBuilder: (BuildContext context) => loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget, fadeInCurve: fadeInCurve, fadeInDuration: fadeInDuration, imageBuilder: (BuildContext context, Widget image) => Container( @@ -225,12 +226,7 @@ class AstromicImage extends StatelessWidget { padding: borderPadding ?? EdgeInsets.zero, margin: EdgeInsets.zero, decoration: BoxDecoration( - border: borderWidth != null - ? Border.all( - width: borderWidth!, - color: borderColor ?? const Color(0xff000000), - ) - : null, + border: borderWidth != null ? Border.all(width: borderWidth!, color: borderColor ?? const Color(0xff000000)) : null, borderRadius: isCircular! ? BorderRadius.circular(10000000) : ((radius as BorderRadius).addToAll((borderWidth ?? 0))), boxShadow: shadow, ), @@ -243,7 +239,9 @@ class AstromicImage extends StatelessWidget { child: isSVG ? finalSVGWidget(size, loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget) : image, ), ), - image: isSVG ? MemoryImage(kTransparentImage) : _imageProvider(assetType, assetRef), + // Provide transparent image placeholder for Octo when rendering raster images; + // real provider is created via _imageProvider which uses ResizeImage + image: isSVG ? MemoryImage(kTransparentImage) : _imageProvider(assetType, assetRef, size, imageQuality ?? 1.0), ), if (overlayColor != null || overlayGradient != null) Container( @@ -252,18 +250,19 @@ class AstromicImage extends StatelessWidget { ], ); } - //s1 -Widgets - //!SECTION - //SECTION - Build Return + // SECTION - Build Return return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { Size size = _calculateSize(constraints); - return SizedBox(width: size.width == double.infinity ? null : size.width, height: size.height == double.infinity ? null : size.height, child: buildImage(size)); + return SizedBox( + width: size.width == double.infinity ? null : size.width, + height: size.height == double.infinity ? null : size.height, + child: buildImage(size), + ); }); - //!SECTION } - //SECTION - Helper Functions + // SECTION - Helper Functions // Get asset reference based on asset type... dynamic _getAssetRef(_DeclaredAssetType type) { switch (type) { @@ -279,43 +278,86 @@ class AstromicImage extends StatelessWidget { } // Detect if asset is an SVG... - bool _isSVG(_DeclaredAssetType type, dynamic ref) => - type == _DeclaredAssetType.bytes ? utf8.decode(ref.sublist(0, ref.length > 10 ? 10 : ref.length), allowMalformed: true).trimLeft().startsWith(' 10 ? 10 : ref.length), allowMalformed: true).trimLeft(); + return prefix.startsWith(' full target resolution, 0.5 -> half resolution + final double factor = imageQuality.clamp(0.1, 1.0); + // If size is infinite for a dimension, we skip resize for that dim (pass 0) + final int? targetWidth = size.width.isFinite ? (size.width * factor).clamp(1, 10000).toInt() : null; + final int? targetHeight = size.height.isFinite ? (size.height * factor).clamp(1, 10000).toInt() : null; + + ImageProvider baseProvider; + switch (type) { + case _DeclaredAssetType.url: + baseProvider = CachedNetworkImageProvider(ref as String, cacheKey: ref); + break; + case _DeclaredAssetType.bytes: + baseProvider = MemoryImage(ref as Uint8List); + break; + case _DeclaredAssetType.path: + baseProvider = AssetImage(ref as String); + break; + case _DeclaredAssetType.fallback: + baseProvider = AssetImage(ref as String); + break; + } + + // If both target dims are null (infinite), return base provider (no resize). + if (targetWidth == null && targetHeight == null) return baseProvider; + + // If one of them is null, supply 0 for that dim to ResizeImage (it will only respect non-zero dim). + final int w = targetWidth ?? 0; + final int h = targetHeight ?? 0; + + // Wrap in ResizeImage so decoding happens closer to desired size. + return ResizeImage.resizeIfNeeded(w, h, baseProvider); + } + // Calculate the sizing of the image... Size _calculateSize(BoxConstraints constraints) { - double? maxAvailablewidth = constraints.maxWidth; - double? maxAvailableheight = constraints.maxHeight; + final double maxAvailableWidth = constraints.maxWidth; + final double maxAvailableHeight = constraints.maxHeight; Size finalSize; switch (sizingMaster!) { case ImageSizingMaster.w: - { - finalSize = Size(fixedWidth ?? (widthSizing!.$1 * maxAvailablewidth).clamp(widthSizing!.$2 ?? 0, widthSizing!.$3 ?? double.infinity), fixedHeight ?? maxAvailableheight); - } + finalSize = Size( + fixedWidth ?? (widthSizing!.$1 * maxAvailableWidth).clamp(widthSizing!.$2 ?? 0, widthSizing!.$3 ?? double.infinity), + fixedHeight ?? maxAvailableHeight, + ); + break; case ImageSizingMaster.h: - { - finalSize = Size(fixedWidth ?? maxAvailablewidth, fixedHeight ?? (heightSizing!.$1 * maxAvailableheight).clamp(heightSizing!.$2 ?? 0, heightSizing!.$3 ?? double.infinity)); - } + finalSize = Size( + fixedWidth ?? maxAvailableWidth, + fixedHeight ?? (heightSizing!.$1 * maxAvailableHeight).clamp(heightSizing!.$2 ?? 0, heightSizing!.$3 ?? double.infinity), + ); + break; } return finalSize; } - //!SECTION + + // Map imageQuality to a reasonable FilterQuality for paint-time resampling + FilterQuality _filterQualityFromImageQuality(double q) { + if (q >= 0.85) return FilterQuality.high; + if (q >= 0.5) return FilterQuality.medium; + return FilterQuality.low; + } } +// Transparent png for svg placeholder final Uint8List kTransparentImage = Uint8List.fromList([ 0x89, 0x50,