Files
astromic_elements/lib/src/Widgets/src/image.widget.dart
2025-03-04 18:57:05 +02:00

380 lines
12 KiB
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://')));
//
return fromPath
? _DeclaredAssetType.path
: fromBytes
? _DeclaredAssetType.bytes
: fromNetwork
? _DeclaredAssetType.url
: _DeclaredAssetType.fallback;
}
class AstromicImage extends StatelessWidget {
//!SECTION
//
AstromicImage({
super.key,
//
this.assetPath,
this.assetURL,
this.assetBytes,
this.assetFallback,
//
this.sizingMaster ,
this.widthSizing,
this.heightSizing,
this.fixedWidth,
this.fixedHeight,
//
this.isCircular,
this.borderWidth,
this.borderColor,
this.borderPadding,
this.radius,
this.shadow,
this.overlayColor,
this.overlayGradient,
//
this.fit,
this.alignment,
this.blendMode,
this.fadeInCurve,
this.fadeInDuration,
//
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');
//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
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
final Color? svgColor;
//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
// 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<String>(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<String>(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: <Widget>[
OctoImage(
key: ValueKey<String>(assetType == _DeclaredAssetType.bytes ? (assetRef as Uint8List).length.toString() : assetRef),
//
width: size.width,
height: size.height == double.infinity? null : size.height,
fit: fit,
alignment: alignment,
filterQuality: FilterQuality.none,
color: svgColor,
colorBlendMode: blendMode ?? (isSVG ? BlendMode.srcATop : null),
//
errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
return errorWidget != null
? errorWidget!(error, stackTrace)
: assetFallback != null
? 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(
width: size.width,
height: size.height == double.infinity? null : size.height,
padding: borderPadding ?? EdgeInsets.zero,
margin: EdgeInsets.zero,
decoration: BoxDecoration(
border: borderWidth != null
? Border.all(
width: borderWidth!,
color: borderColor ?? const Color(0xff000000),
)
: null,
borderRadius: isCircular! ? BorderRadius.circular(10000000) : radius,
boxShadow: shadow,
),
child: isCircular!
? ClipOval(
child: isSVG ? finalSVGWidget(size, loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget) : image,
)
: ClipRRect(
borderRadius: isCircular! ? BorderRadius.circular(10000000) : radius!,
child: isSVG ? finalSVGWidget(size, loadingWidget != null ? loadingWidget!(null, null) : defaultLoadingWidget) : 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),
),
],
);
}
//s1 -Widgets
//!SECTION
//SECTION - Build Return
return LayoutBuilder(builder: (BuildContext context, BoxConstraints 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>[
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
0x00,
0x00,
0x0D,
0x49,
0x48,
0x44,
0x52,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x06,
0x00,
0x00,
0x00,
0x1F,
0x15,
0xC4,
0x89,
0x00,
0x00,
0x00,
0x0A,
0x49,
0x44,
0x41,
0x54,
0x78,
0x9C,
0x63,
0x00,
0x01,
0x00,
0x00,
0x05,
0x00,
0x01,
0x0D,
0x0A,
0x2D,
0xB4,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4E,
0x44,
0xAE,
0x42,
0x60,
0x82,
]);