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
//s2 Core Package Imports
import 'dart:ui';
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 +36,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 +50,6 @@ _DeclaredAssetType _parseAssetType(
}
class AstromicImage extends StatelessWidget {
//!SECTION
//
AstromicImage({
super.key,
//
@@ -79,6 +78,7 @@ class AstromicImage extends StatelessWidget {
this.blendMode,
this.fadeInCurve,
this.fadeInDuration,
this.imageQuality = 0.5,
//
this.svgColor,
//
@@ -103,6 +103,7 @@ 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
final String? assetPath;
@@ -134,6 +135,7 @@ class AstromicImage extends StatelessWidget {
final BlendMode? blendMode;
final Curve? fadeInCurve;
final Duration? fadeInDuration;
final double? imageQuality; // 0.0 = very blurry, 1.0 = full quality
// S1 -- SVG FILTERS
final Color? svgColor;
// S1 -- STATE WIDGETS
@@ -143,22 +145,20 @@ class AstromicImage extends StatelessWidget {
@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
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(
// 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<String>(assetRef),
width: size.width,
@@ -171,9 +171,9 @@ class AstromicImage extends StatelessWidget {
),
alignment: alignment!,
colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null,
)
: assetType == _DeclaredAssetType.url
? SvgPicture.network(
);
} else if (assetType == _DeclaredAssetType.url && assetRef is String) {
return SvgPicture.network(
assetRef,
key: ValueKey<String>(assetRef),
width: size.width,
@@ -186,23 +186,28 @@ class AstromicImage extends StatelessWidget {
),
alignment: alignment!,
colorFilter: svgColor != null ? ColorFilter.mode(svgColor!, blendMode ?? BlendMode.srcATop) : null,
)
: 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: <Widget>[
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,
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 +215,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 +228,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 +241,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,15 +252,16 @@ class AstromicImage extends StatelessWidget {
],
);
}
//s1 -Widgets
//!SECTION
// 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
@@ -279,43 +280,97 @@ 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('<svg') : (ref?.endsWith('.svg') ?? false);
// Get image provider based on type
ImageProvider _imageProvider(_DeclaredAssetType type, dynamic ref) {
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);
bool _isSVG(_DeclaredAssetType type, dynamic ref) {
try {
if (type == _DeclaredAssetType.bytes && ref is Uint8List) {
final String prefix = utf8.decode(ref.sublist(0, ref.length > 10 ? 10 : ref.length), allowMalformed: true).trimLeft();
return prefix.startsWith('<svg');
}
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...
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(<int>[
0x89,
0x50,

View File

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