This commit is contained in:
2025-02-11 15:26:44 +02:00
parent 4a0ff120f7
commit 40f17efd30
28 changed files with 580 additions and 781 deletions

View File

@@ -1,217 +1,313 @@
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
//s1 Imports
//s2 Core Package Imports
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';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:octo_image/octo_image.dart';
//s2 Dependancies Imports
//s3 Routes
//s3 Services
//s3 Models
//s1 Exports
enum ImageSizingMaster {
w,
h,
}
enum _DeclaredAssetType {
path,
url,
bytes,
fallback,
}
_DeclaredAssetType _parseAssetType(
String? path,
String? url,
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://')));
Widget astromicImage(
BuildContext context, {
//S1 -- Asset
String? assetPath,
String? assetURL,
Uint8List? assetBytes,
String? assetFallback,
//
//S1 -- Sizing | Width
double? wFactor, // variable width ratio
double? minW, // Min width allowed
double? maxW, // Max width allowed
double? fixedWidth, // Only used when uisng height variables
//S1 -- Sizing | Width
bool useHeight = false, // use height constrains
double? hFactor, // Variable height ratio
double? minH, // Min Height allowed
double? maxH, // Max height allowed
double? fixedHeight, // Only used when using width variables
return fromPath
? _DeclaredAssetType.path
: fromBytes
? _DeclaredAssetType.bytes
: fromNetwork
? _DeclaredAssetType.url
: _DeclaredAssetType.fallback;
}
class AstromicImage extends StatelessWidget {
//SECTION - Widget Arguments
//S1 -- Assets
final String? assetPath;
final String? assetURL;
final Uint8List? assetBytes;
final String? assetFallback;
//S1 -- Sizing
final ImageSizingMaster? sizingMaster;
final (double factor, double? min, double? max)? widthSizing;
/// Used when the width is Master and want to set fixed width OR if height is Master and want to constraint the width
final double? fixedWidth;
final (double factor, double? min, double? max)? heightSizing;
/// 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
bool circular = false,
double? border,
Color? borderColor,
EdgeInsetsGeometry? borderPadding,
BorderRadiusGeometry radius = BorderRadius.zero,
List<BoxShadow>? shadow,
final bool? isCircular;
final double? borderWidth;
final Color? borderColor;
final EdgeInsetsGeometry? borderPadding;
final BorderRadiusGeometry? radius;
final List<BoxShadow>? shadow;
final Color? overlayColor;
final Gradient? overlayGradient;
//S1 -- CONFIGURATIONS
final Alignment? alignment;
final BoxFit? fit;
final BlendMode? blendMode;
final Curve? fadeInCurve;
final Duration? fadeInDuration;
//S1 -- SVG FILTERS
Color? color,
BlendMode? blend,
//S1 -- CONFIGS
BoxFit fit = BoxFit.cover,
Alignment alignment = Alignment.center,
LinearGradient? linearGradient,
//S1 -- STATE
Widget? loadingWidget,
Widget? errorWidget,
}) {
final Color? svgColor;
//S1 -- STATE WIDGETS
final Widget Function(int? loadedBytes, int? totalBytesToLoad)? loadingWidget;
final Widget Function(dynamic error, StackTrace? stackTrace)? errorWidget;
//!SECTION
//
assert(minW == null || maxW == null, "Please specify only one width constrain");
assert(minH == null || maxH == null, "Please specify only one height constrain");
assert(!useHeight || (hFactor != null), "Please specify The height factor and constrains");
assert(
(assetPath != null && assetBytes == null && assetURL == null) ||
(assetPath == null && assetBytes != null && assetURL == null) ||
(assetPath == null && assetBytes == null && assetURL != null) ||
(assetFallback != null && assetFallback != ''),
"Please specify only one Asset Source");
//
//
bool fromPath = (assetPath != null && assetPath != '');
bool fromBytes = (assetBytes != null && assetBytes.isNotEmpty);
bool fromNetwork = (assetURL != null && assetURL != '' && Uri.tryParse(assetURL) != null);
//
String finalAssetKey = fromPath
? assetPath
: fromNetwork
? assetURL
: fromBytes
? assetBytes.length.toString()
: 'N/A';
//
bool isFallback = (assetBytes == null && assetURL == null && assetPath == null && assetFallback != null);
bool isSVG = fromPath
? assetPath.endsWith('.svg')
: fromNetwork
? assetURL.endsWith('.svg')
: isFallback
? assetFallback.endsWith('.svg')
: false;
//
double h = MediaQuery.of(context).size.height;
double? fh = hFactor != null ? hFactor * h : null;
double w = MediaQuery.of(context).size.width;
double? fw = wFactor != null ? wFactor * w : null;
AstromicImage({
super.key,
//
this.assetPath,
this.assetURL,
this.assetBytes,
this.assetFallback,
//
this.sizingMaster = ImageSizingMaster.w,
this.widthSizing,
this.heightSizing,
this.fixedWidth,
this.fixedHeight,
//
this.isCircular = false,
this.borderWidth,
this.borderColor,
this.borderPadding,
this.radius = BorderRadius.zero,
this.shadow,
this.overlayColor,
this.overlayGradient,
//
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.blendMode,
this.fadeInCurve = Curves.ease,
this.fadeInDuration = const Duration(milliseconds: 250),
//
this.svgColor,
//
this.loadingWidget,
this.errorWidget,
}) :
// Assert that a source is provided, or provide a fallback source..
assert(((assetPath?.isNotEmpty ?? false) || (assetURL?.isNotEmpty ?? false) || (assetBytes?.isNotEmpty ?? false)) || (assetFallback?.isNotEmpty ?? false),
"Please specify a source or provide a fallback."),
// Assert that only ONE source is provided...
assert(
(assetPath != null && assetBytes == null && assetURL == null) ||
(assetPath == null && assetBytes != null && assetURL == null) ||
(assetPath == null && assetBytes == null && assetURL != null),
"Please specify only ONE Asset Source"),
// Assert that correct sizing plan is provided...
assert((sizingMaster == ImageSizingMaster.w && (widthSizing != null || fixedWidth != null)) || (sizingMaster == ImageSizingMaster.h && (heightSizing != null || fixedHeight != null)),
"Please provide the correct sizing configurations based on the SizingMaster choosen");
//
double? finalW = useHeight
? fixedWidth
: fw != null
? fw > (maxW ?? double.infinity)
? maxW
: fw < (minW ?? 0)
? minW
: fw
: null;
double? finalH = !useHeight
? fixedHeight
: fh != null
? fh > (maxH ?? double.infinity)
? maxH
: fh < (minH ?? 0)
? minH
: fh
: null;
//
Widget finalError = errorWidget ??
const Center(
child: Text("Error has happened.."),
);
//
Widget finalLoading = loadingWidget ?? Container();
//
ImageProvider finalImageProvider = isFallback
? AssetImage(
assetFallback,
)
: fromPath
? AssetImage(
assetPath,
)
: fromBytes
? MemoryImage(assetBytes)
: fromNetwork
? CachedNetworkImageProvider(
assetURL,
cacheKey: assetURL,
)
: MemoryImage(kTransparentImage) as ImageProvider;
//
Widget? finalSVGWidget = fromPath
? SvgPicture.asset(
assetPath,
key: ValueKey(assetPath),
width: finalW,
height: finalH,
fit: fit,
placeholderBuilder: (_) => SizedBox(
width: finalW,
height: finalH,
child: finalLoading,
),
alignment: alignment,
colorFilter: color != null ? ColorFilter.mode(color, blend ?? BlendMode.srcATop) : null,
)
: fromNetwork
? SvgPicture.network(
assetURL,
key: ValueKey(assetURL),
width: finalW,
height: finalH,
fit: fit,
placeholderBuilder: (_) => SizedBox(
width: finalW,
height: finalH,
child: finalLoading,
@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
// Default Loading Widget
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),
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;
Widget buildImage(Size size) {
return Stack(
children: [
OctoImage(
key: ValueKey(assetType == _DeclaredAssetType.bytes ? (assetRef as Uint8List).length.toString() : assetRef),
//
width: size.width,
height: size.height,
fit: fit,
alignment: alignment,
filterQuality: FilterQuality.none,
color: svgColor,
colorBlendMode: blendMode ?? (isSVG ? BlendMode.srcATop : null),
//
errorBuilder: (context, error, stackTrace) {
debugPrint("AstromicImage Error: $error");
return errorWidget != null
? errorWidget!(error, stackTrace)
: assetFallback != null
? Image.asset(assetFallback!)
: defaultErrorWidget(error);
},
//
progressIndicatorBuilder: (_, bytes) => SizedBox(
width: size.width,
height: size.height,
child: loadingWidget != null ? loadingWidget!(bytes?.cumulativeBytesLoaded, bytes?.expectedTotalBytes) : defaultLoadingWidget,
),
placeholderBuilder: (context) => loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget,
fadeInCurve: fadeInCurve,
fadeInDuration: fadeInDuration,
imageBuilder: (context, image) => Container(
width: size.width,
height: size.height,
padding: borderPadding ?? EdgeInsets.zero,
margin: EdgeInsets.zero,
decoration: BoxDecoration(
border: borderWidth != null
? Border.all(
strokeAlign: BorderSide.strokeAlignInside,
width: borderWidth!,
color: borderColor ?? const Color(0xff000000),
)
: null,
borderRadius: isCircular! ? BorderRadius.circular(10000000) : radius,
boxShadow: shadow,
),
alignment: alignment,
colorFilter: color != null ? ColorFilter.mode(color, blend ?? BlendMode.srcATop) : null,
)
: null;
//
return SizedBox(
width: finalW,
height: finalH,
child: Stack(
children: [
OctoImage(
key: ValueKey(finalAssetKey),
//
width: finalW,
height: finalH,
fit: fit,
alignment: alignment,
filterQuality: FilterQuality.none,
color: color,
colorBlendMode: blend ?? (isSVG ? BlendMode.srcATop : null),
//
errorBuilder: (context, error, stackTrace) {
return finalError;
},
//
progressIndicatorBuilder: (a, b) => SizedBox(
width: finalW,
height: finalH,
child: finalLoading,
),
imageBuilder: (context, image) => Container(
width: finalW,
height: finalH,
padding: borderPadding ?? EdgeInsets.zero,
margin: EdgeInsets.zero,
decoration: BoxDecoration(
border: border != null
? Border.all(
strokeAlign: BorderSide.strokeAlignInside,
width: border,
color: borderColor ?? const Color(0xff000000),
child: isCircular!
? ClipOval(
child: isSVG ? finalSVGWidget(size, loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget) : image,
)
: null,
borderRadius: circular ? BorderRadius.circular(10000) : radius,
boxShadow: shadow,
: ClipRRect(
borderRadius: isCircular! ? BorderRadius.circular(10000000) : radius!,
child: isSVG ? finalSVGWidget(size, loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget) : image,
),
),
child: ClipRRect(
borderRadius: circular ? BorderRadius.circular(10000) : radius,
child: isSVG ? finalSVGWidget : image,
image: isSVG ? MemoryImage(kTransparentImage) : _imageProvider(assetType, assetRef),
),
if (overlayColor != null || overlayGradient != null)
Container(
decoration: BoxDecoration(color: overlayGradient != null ? null : overlayColor, gradient: overlayGradient, borderRadius: radius),
),
),
image: isSVG ? MemoryImage(kTransparentImage) : finalImageProvider,
),
if (linearGradient != null)
Container(
decoration: BoxDecoration(gradient: linearGradient, borderRadius: radius),
),
],
),
);
],
);
}
//s1 -Widgets
//!SECTION
//SECTION - Build Return
return LayoutBuilder(builder: (context, constraints) {
Size size = _calculateSize(constraints);
return SizedBox(width: size.width, height: size.height, child: buildImage(size));
});
//!SECTION
}
//SECTION - Helper Functions
// Get asset reference based on asset type...
dynamic _getAssetRef(_DeclaredAssetType type) {
switch (type) {
case _DeclaredAssetType.path:
return assetPath;
case _DeclaredAssetType.bytes:
return assetBytes;
case _DeclaredAssetType.url:
return assetURL;
case _DeclaredAssetType.fallback:
return assetFallback;
}
}
// 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);
}
}
// Calculate the sizing of the image...
Size _calculateSize(BoxConstraints constraints) {
double? maxAvailablewidth = constraints.maxWidth;
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);
}
case ImageSizingMaster.h:
{
finalSize = Size(fixedWidth ?? maxAvailablewidth, fixedHeight ?? (heightSizing!.$1 * maxAvailableheight).clamp(heightSizing!.$2 ?? 0, heightSizing!.$3 ?? double.infinity));
}
}
return finalSize;
}
//!SECTION
}
final Uint8List kTransparentImage = Uint8List.fromList(<int>[

View File

@@ -58,75 +58,65 @@ class AstromicWidgets {
);
//S1 -- IMAGE
static Widget image(
BuildContext context, {
//S1 -- Asset
static Widget image({
String? assetPath,
String? assetURL,
Uint8List? assetBytes,
String? assetFallback,
//S1 -- Sizing | Width
double? wFactor,
double? minW,
double? maxW,
ImageSizingMaster? sizingMaster,
(double factor, double? min, double? max)? widthSizing,
/// Used when the width is Master and want to set fixed width OR if height is Master and want to constraint the width
double? fixedWidth,
//S1 -- Sizing | Width
bool useHeight = false,
double? hFactor,
double? minH,
double? maxH,
(double factor, double? min, double? max)? heightSizing,
/// Used when the height is Master and want to set fixed height OR if width is Master and want to constraint the height
double? fixedHeight,
//S1 -- STYLING
bool circular = false,
double? border,
bool? isCircular,
double? borderWidth,
Color? borderColor,
EdgeInsetsGeometry? borderPadding,
BorderRadiusGeometry radius = BorderRadius.zero,
BorderRadiusGeometry? radius,
List<BoxShadow>? shadow,
//S1 -- SVG FILTERS
Color? color,
BlendMode? blend,
//S1 -- CONFIGS
BoxFit fit = BoxFit.cover,
Alignment alignment = Alignment.center,
LinearGradient? linearGradient,
//S1 -- STATE
Widget? loadingWidget,
Widget? errorWidget,
Color? overlayColor,
Gradient? overlayGradient,
Alignment? alignment,
BoxFit? fit,
BlendMode? blendMode,
Curve? fadeInCurve,
Duration? fadeInDuration,
Color? svgColor,
Widget Function(int? loadedBytes, int? totalBytesToLoad)? loadingWidget,
Widget Function(dynamic error, StackTrace? stackTrace)? errorWidget,
}) =>
astromicImage(
context,
//
AstromicImage(
assetPath: assetPath,
assetURL: assetURL,
assetBytes: assetBytes,
assetFallback: assetFallback,
//
wFactor: wFactor,
minW: minW,
maxW: maxW,
sizingMaster: sizingMaster,
widthSizing: widthSizing,
/// Used when the width is Master and want to set fixed width OR if height is Master and want to constraint the width
fixedWidth: fixedWidth,
//
useHeight: useHeight,
hFactor: hFactor,
minH: minH,
maxH: maxH,
heightSizing: heightSizing,
/// Used when the height is Master and want to set fixed height OR if width is Master and want to constraint the height
fixedHeight: fixedHeight,
//
circular: circular,
border: border,
isCircular: isCircular,
borderWidth: borderWidth,
borderColor: borderColor,
borderPadding: borderPadding,
radius: radius,
shadow: shadow,
//
color: color,
blend: blend,
//
fit: fit,
overlayColor: overlayColor,
overlayGradient: overlayGradient,
alignment: alignment,
linearGradient: linearGradient,
//
fit: fit,
blendMode: blendMode,
fadeInCurve: fadeInCurve,
fadeInDuration: fadeInDuration,
svgColor: svgColor,
loadingWidget: loadingWidget,
errorWidget: errorWidget,
);