This commit is contained in:
2025-12-20 21:26:38 +02:00
3 changed files with 153 additions and 96 deletions

View File

@@ -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}} {"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}}

View File

@@ -1,8 +1,10 @@
//s1 Imports //s1 Imports
//s2 Core Package Imports //s2 Core Package Imports
import 'dart:ui';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
//s2 1st-party Package Imports //s2 1st-party Package Imports
//s2 3rd-party Package Imports //s2 3rd-party Package Imports
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
@@ -34,11 +36,10 @@ _DeclaredAssetType _parseAssetType(
Uint8List? bytes, Uint8List? bytes,
String? fallback, String? fallback,
) { ) {
bool fromPath = (path != null && path != ''); final bool fromPath = (path != null && path.isNotEmpty);
bool fromBytes = (bytes != null && bytes.isNotEmpty); final 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 fromNetwork = (url != null && url.isNotEmpty && Uri.tryParse(url)?.hasAbsolutePath == true && (url.startsWith('http://') || url.startsWith('https://')));
//
return fromPath return fromPath
? _DeclaredAssetType.path ? _DeclaredAssetType.path
: fromBytes : fromBytes
@@ -49,8 +50,6 @@ _DeclaredAssetType _parseAssetType(
} }
class AstromicImage extends StatelessWidget { class AstromicImage extends StatelessWidget {
//!SECTION
//
AstromicImage({ AstromicImage({
super.key, super.key,
// //
@@ -79,6 +78,7 @@ class AstromicImage extends StatelessWidget {
this.blendMode, this.blendMode,
this.fadeInCurve, this.fadeInCurve,
this.fadeInDuration, this.fadeInDuration,
this.imageQuality = 0.5,
// //
this.svgColor, this.svgColor,
// //
@@ -103,6 +103,7 @@ class AstromicImage extends StatelessWidget {
(sizingMaster == ImageSizingMaster.w && (widthSizing != null || fixedWidth != null)) || (sizingMaster == ImageSizingMaster.h && (heightSizing != null || fixedHeight != null)), (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.', 'Please provide the correct sizing configurations based on the SizingMaster chosen.',
); );
// SECTION - Widget Arguments // SECTION - Widget Arguments
// S1 -- Assets // S1 -- Assets
final String? assetPath; final String? assetPath;
@@ -134,6 +135,7 @@ class AstromicImage extends StatelessWidget {
final BlendMode? blendMode; final BlendMode? blendMode;
final Curve? fadeInCurve; final Curve? fadeInCurve;
final Duration? fadeInDuration; final Duration? fadeInDuration;
final double? imageQuality; // 0.0 = very blurry, 1.0 = full quality
// S1 -- SVG FILTERS // S1 -- SVG FILTERS
final Color? svgColor; final Color? svgColor;
// S1 -- STATE WIDGETS // S1 -- STATE WIDGETS
@@ -143,22 +145,20 @@ class AstromicImage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// SECTION - Build Setup // SECTION - Build Setup
//s1 -Values final _DeclaredAssetType assetType = _parseAssetType(assetPath, assetURL, assetBytes, assetFallback);
_DeclaredAssetType assetType = _parseAssetType(assetPath, assetURL, assetBytes, assetFallback); final dynamic assetRef = _getAssetRef(assetType);
dynamic assetRef = _getAssetRef(assetType); final bool isSVG = _isSVG(assetType, assetRef);
bool isSVG = _isSVG(assetType, assetRef);
//s1 -Values
//
//s1 -Widgets
// Default Loading Widget // Default Loading Widget
Widget defaultLoadingWidget = const Text('Loading...'); final Widget defaultLoadingWidget = const Text('Loading...');
// Default Error Widget // Default Error Widget
Widget defaultErrorWidget(dynamic error) => Text('An error has happened: $error'); Widget defaultErrorWidget(dynamic error) => Text('An error has happened: $error');
// Get final svg widget // final svg widget (SVGs already handle width/height)
Widget? finalSVGWidget(Size size, Widget? loadingWidget) => assetType == _DeclaredAssetType.path Widget? finalSVGWidget(Size size, Widget? loadingWidget) {
? SvgPicture.asset( if (assetType == _DeclaredAssetType.path && assetRef is String) {
return SvgPicture.asset(
assetRef, assetRef,
key: ValueKey<String>(assetRef), key: ValueKey<String>(assetRef),
width: size.width, width: size.width,
@@ -171,9 +171,9 @@ class AstromicImage extends StatelessWidget {
), ),
alignment: alignment!, alignment: alignment!,
colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null, colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null,
) );
: assetType == _DeclaredAssetType.url } else if (assetType == _DeclaredAssetType.url && assetRef is String) {
? SvgPicture.network( return SvgPicture.network(
assetRef, assetRef,
key: ValueKey<String>(assetRef), key: ValueKey<String>(assetRef),
width: size.width, width: size.width,
@@ -186,23 +186,28 @@ class AstromicImage extends StatelessWidget {
), ),
alignment: alignment!, alignment: alignment!,
colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null, colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null,
) );
: null; }
return null;
}
Widget buildImage(Size size) { Widget buildImage(Size size) {
// choose filter quality from imageQuality for paint-time interpolation
final FilterQuality filterQuality = _filterQualityFromImageQuality(imageQuality ?? 1.0);
return Stack( return Stack(
children: <Widget>[ children: <Widget>[
OctoImage( OctoImage(
key: ValueKey<String>(assetType == _DeclaredAssetType.bytes ? (assetRef as Uint8List).length.toString() : assetRef), key: ValueKey<String>(
// assetType == _DeclaredAssetType.bytes ? 'bytes-${(assetRef as Uint8List).lengthInBytes}' : (assetRef?.toString() ?? 'unknown'),
),
width: size.width, width: size.width,
height: size.height == double.infinity ? null : size.height, height: size.height == double.infinity ? null : size.height,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
filterQuality: FilterQuality.none, filterQuality: filterQuality,
color: svgColor, color: svgColor,
colorBlendMode: blendMode ?? (isSVG ? BlendMode.srcATop : null), colorBlendMode: blendMode ?? (isSVG ? BlendMode.srcATop : null),
//
errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
return errorWidget != null return errorWidget != null
? errorWidget!(error, stackTrace) ? errorWidget!(error, stackTrace)
@@ -210,13 +215,11 @@ class AstromicImage extends StatelessWidget {
? Image.asset(assetFallback!) ? Image.asset(assetFallback!)
: defaultErrorWidget(error); : defaultErrorWidget(error);
}, },
//
progressIndicatorBuilder: (_, ImageChunkEvent? bytes) => SizedBox( progressIndicatorBuilder: (_, ImageChunkEvent? bytes) => SizedBox(
width: size.width, width: size.width,
height: size.height == double.infinity ? null : size.height, height: size.height == double.infinity ? null : size.height,
child: loadingWidget != null ? loadingWidget!(bytes?.cumulativeBytesLoaded, bytes?.expectedTotalBytes) : defaultLoadingWidget, child: loadingWidget != null ? loadingWidget!(bytes?.cumulativeBytesLoaded, bytes?.expectedTotalBytes) : defaultLoadingWidget,
), ),
// placeholderBuilder: (BuildContext context) => loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget,
fadeInCurve: fadeInCurve, fadeInCurve: fadeInCurve,
fadeInDuration: fadeInDuration, fadeInDuration: fadeInDuration,
imageBuilder: (BuildContext context, Widget image) => Container( imageBuilder: (BuildContext context, Widget image) => Container(
@@ -225,12 +228,7 @@ class AstromicImage extends StatelessWidget {
padding: borderPadding ?? EdgeInsets.zero, padding: borderPadding ?? EdgeInsets.zero,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
decoration: BoxDecoration( decoration: BoxDecoration(
border: borderWidth != null border: borderWidth != null ? Border.all(width: borderWidth!, color: borderColor ?? const Color(0xff000000)) : null,
? Border.all(
width: borderWidth!,
color: borderColor ?? const Color(0xff000000),
)
: null,
borderRadius: isCircular! ? BorderRadius.circular(10000000) : ((radius as BorderRadius).addToAll((borderWidth ?? 0))), borderRadius: isCircular! ? BorderRadius.circular(10000000) : ((radius as BorderRadius).addToAll((borderWidth ?? 0))),
boxShadow: shadow, boxShadow: shadow,
), ),
@@ -243,7 +241,9 @@ class AstromicImage extends StatelessWidget {
child: isSVG ? finalSVGWidget(size, loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget) : image, 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) if (overlayColor != null || overlayGradient != null)
Container( Container(
@@ -252,15 +252,16 @@ class AstromicImage extends StatelessWidget {
], ],
); );
} }
//s1 -Widgets
//!SECTION
// SECTION - Build Return // SECTION - Build Return
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
Size size = _calculateSize(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
@@ -279,43 +280,97 @@ class AstromicImage extends StatelessWidget {
} }
// Detect if asset is an SVG... // Detect if asset is an SVG...
bool _isSVG(_DeclaredAssetType type, dynamic ref) => bool _isSVG(_DeclaredAssetType type, dynamic ref) {
type == _DeclaredAssetType.bytes ? utf8.decode(ref.sublist(0, ref.length > 10 ? 10 : ref.length), allowMalformed: true).trimLeft().startsWith('<svg') : (ref?.endsWith('.svg') ?? false); try {
if (type == _DeclaredAssetType.bytes && ref is Uint8List) {
// Get image provider based on type final String prefix = utf8.decode(ref.sublist(0, ref.length > 10 ? 10 : ref.length), allowMalformed: true).trimLeft();
ImageProvider _imageProvider(_DeclaredAssetType type, dynamic ref) { return prefix.startsWith('<svg');
switch (type) {
case _DeclaredAssetType.fallback:
return AssetImage(ref);
case _DeclaredAssetType.path:
return AssetImage(ref);
case _DeclaredAssetType.bytes:
return MemoryImage(ref);
case _DeclaredAssetType.url:
return CachedNetworkImageProvider(ref, cacheKey: ref);
} }
if (ref is String) return ref.toLowerCase().endsWith('.svg');
return false;
} catch (_) {
return false;
}
}
// Build image provider and ensure it's resized to (or near) the rendering size.
ImageProvider _imageProvider(
_DeclaredAssetType type,
dynamic ref,
Size size,
double imageQuality,
) {
// imageQuality is used as a downscale factor: 1.0 -> full target resolution, 0.5 -> half resolution
final double factor = imageQuality.clamp(0.1, 1.0);
final double dpr = PlatformDispatcher.instance.views.first.devicePixelRatio;
final int? targetWidth = size.width.isFinite ? (size.width * factor * dpr).clamp(1, 4096).toInt() : null;
final int? targetHeight = size.height.isFinite ? (size.height * factor * dpr).clamp(1, 4096).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:
case _DeclaredAssetType.fallback:
baseProvider = AssetImage(ref as String);
break;
}
// ✅ Skip resizing for AssetImage (Flutter handles resolution variants)
if (baseProvider is AssetImage) {
return baseProvider;
}
// 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 (ResizeImage respects 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... // Calculate the sizing of the image...
Size _calculateSize(BoxConstraints constraints) { Size _calculateSize(BoxConstraints constraints) {
double? maxAvailablewidth = constraints.maxWidth; final double maxAvailableWidth = constraints.maxWidth;
double? maxAvailableheight = constraints.maxHeight; final double maxAvailableHeight = constraints.maxHeight;
Size finalSize; Size finalSize;
switch (sizingMaster!) { switch (sizingMaster!) {
case ImageSizingMaster.w: case ImageSizingMaster.w:
{ finalSize = Size(
finalSize = Size(fixedWidth ?? (widthSizing!.$1 * maxAvailablewidth).clamp(widthSizing!.$2 ?? 0, widthSizing!.$3 ?? double.infinity), fixedHeight ?? maxAvailableheight); fixedWidth ?? (widthSizing!.$1 * maxAvailableWidth).clamp(widthSizing!.$2 ?? 0, widthSizing!.$3 ?? double.infinity),
} fixedHeight ?? maxAvailableHeight,
);
break;
case ImageSizingMaster.h: case ImageSizingMaster.h:
{ finalSize = Size(
finalSize = Size(fixedWidth ?? maxAvailablewidth, fixedHeight ?? (heightSizing!.$1 * maxAvailableheight).clamp(heightSizing!.$2 ?? 0, heightSizing!.$3 ?? double.infinity)); fixedWidth ?? maxAvailableWidth,
} fixedHeight ?? (heightSizing!.$1 * maxAvailableHeight).clamp(heightSizing!.$2 ?? 0, heightSizing!.$3 ?? double.infinity),
);
break;
} }
return finalSize; 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(<int>[ final Uint8List kTransparentImage = Uint8List.fromList(<int>[
0x89, 0x89,
0x50, 0x50,

View File

@@ -46,6 +46,7 @@ class AstromicWidgets {
Color? svgColor, Color? svgColor,
Widget Function(int? loadedBytes, int? totalBytesToLoad)? loadingWidget, Widget Function(int? loadedBytes, int? totalBytesToLoad)? loadingWidget,
Widget Function(dynamic error, StackTrace? stackTrace)? errorWidget, Widget Function(dynamic error, StackTrace? stackTrace)? errorWidget,
double? imageQuality,
}) => }) =>
AstromicImage( AstromicImage(
assetPath: assetPath, assetPath: assetPath,
@@ -77,6 +78,7 @@ class AstromicWidgets {
svgColor: svgColor, svgColor: svgColor,
loadingWidget: loadingWidget, loadingWidget: loadingWidget,
errorWidget: errorWidget, errorWidget: errorWidget,
imageQuality: imageQuality,
); );
/// Customized Blur widget for the Astromic system. /// Customized Blur widget for the Astromic system.