Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion modules/ensemble/lib/framework/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/util/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';

/// misc models

Expand All @@ -19,14 +20,20 @@ class BackgroundImage {
BoxFit? fit,
Alignment? alignment,
dynamic fallback,
dynamic loadingWidget,
BaseCacheManager? cacheManager,
}) : _fit = fit ?? BoxFit.cover,
_alignment = alignment ?? Alignment.center,
_fallback = fallback;
_fallback = fallback,
_loadingWidget = loadingWidget,
_cacheManager = cacheManager;

final String _source;
final BoxFit _fit;
final Alignment _alignment;
final dynamic _fallback;
final dynamic _loadingWidget;
final BaseCacheManager? _cacheManager;

DecorationImage getImageAsDecorated() {
ImageProvider imageProvider;
Expand All @@ -52,6 +59,9 @@ class BackgroundImage {
final Widget? fallbackWidget = _fallback != null
? scopeManager?.buildWidgetFromDefinition(_fallback)
: null;
final Widget? loadingWidget = _loadingWidget != null
? scopeManager?.buildWidgetFromDefinition(_loadingWidget)
: null;

if (Utils.isUrl(_source)) {
String assetName = Utils.getAssetName(_source);
Expand All @@ -68,6 +78,8 @@ class BackgroundImage {
imageUrl: _source,
fit: _fit,
alignment: _alignment,
cacheManager: _cacheManager,
placeholder: loadingWidget != null ? (_, __) => loadingWidget : null,
errorWidget:
fallbackWidget != null ? (_, __, ___) => fallbackWidget : null,
);
Expand Down
9 changes: 8 additions & 1 deletion modules/ensemble/lib/framework/widget/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Image extends StatelessWidget {
this.fit,
this.errorBuilder,
this.placeholderBuilder,
this.loadingWidget,
this.colorFilter,
this.networkCacheManager});

Expand All @@ -33,6 +34,9 @@ class Image extends StatelessWidget {
// applicable for network image only
final Widget Function(BuildContext, String)? placeholderBuilder;

// optional widget while the image is loading
final Widget? loadingWidget;

// optional cache manager for network image
final BaseCacheManager? networkCacheManager;

Expand Down Expand Up @@ -60,7 +64,10 @@ class Image extends StatelessWidget {
fit: fit,

// placeholder while the image is loading
placeholder: placeholderBuilder,
placeholder: placeholderBuilder != null || loadingWidget != null
? (context, url) =>
loadingWidget ?? placeholderBuilder!(context, url)
: null,
errorWidget: errorBuilder != null
? (context, url, error) => errorBuilder!(error.toString())
: null,
Expand Down
26 changes: 21 additions & 5 deletions modules/ensemble/lib/widget/avatar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import 'package:ensemble/widget/helpers/box_wrapper.dart';
import 'package:ensemble/widget/helpers/controllers.dart';
import 'package:ensemble/widget/image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:yaml/yaml.dart';

class Avatar extends EnsembleWidget<AvatarController> {
static const type = 'Avatar';

const Avatar._(super.controller, {super.key});
const Avatar._(super.controller);

factory Avatar.build(dynamic controller) => Avatar._(
controller is AvatarController ? controller : AvatarController());
Expand All @@ -43,6 +44,8 @@ class AvatarController extends EnsembleBoxController {
String? source;
BoxFit? fit;
Color? placeholderColor;
dynamic loadingWidget;
BaseCacheManager? networkCacheManager;
ColorFilterComposite? colorFilter;
bool allowRedirect = true;
bool? useGrayscaleFilter;
Expand All @@ -57,7 +60,9 @@ class AvatarController extends EnsembleBoxController {

@override
Map<String, Function> getters() {
return {};
return {
'loadingWidget': () => loadingWidget,
};
}

@override
Expand All @@ -73,6 +78,9 @@ class AvatarController extends EnsembleBoxController {
'source': (value) => source = Utils.optionalString(value),
'fit': (value) => fit = Utils.getBoxFit(value),
'placeholderColor': (value) => placeholderColor = Utils.getColor(value),
'loadingWidget': (value) => loadingWidget = value,
'networkCacheManager': (value) =>
networkCacheManager = value is BaseCacheManager ? value : null,
'allowRedirect': (value) =>
allowRedirect = Utils.getBool(value, fallback: true),
'variant': (value) => variant = AvatarVariant.values.from(value),
Expand Down Expand Up @@ -300,9 +308,17 @@ class AvatarState extends EnsembleWidgetState<Avatar>
source: source,
fit: widget.controller.fit,
colorFilter: widget.controller.colorFilter,
networkCacheManager: EnsembleImageCacheManager.instanceFor(
allowRedirect: widget.controller.allowRedirect,
),
loadingWidget: getScopeManager() != null &&
widget.controller.loadingWidget != null
? getScopeManager()!
.buildWidgetFromDefinition(widget.controller.loadingWidget)
: null,
networkCacheManager: widget.controller.allowRedirect
? widget.controller.networkCacheManager ??
EnsembleImageCacheManager.instanceFor()
: EnsembleImageCacheManager.instanceFor(
allowRedirect: false,
),
placeholderBuilder: (_, __) =>
ColoredBoxPlaceholder(color: widget.controller.placeholderColor),
errorBuilder: (_) => _buildFallback());
Expand Down
80 changes: 57 additions & 23 deletions modules/ensemble/lib/widget/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class EnsembleImage extends StatefulWidget
'resizedWidth': () => _controller.resizedWidth,
'resizedHeight': () => _controller.resizedHeight,
'placeholderColor': () => _controller.placeholderColor,
'loadingWidget': () => _controller.loadingWidget,
'networkCacheManager': () => _controller.networkCacheManager,
'colorFilter': () =>
_controller.colorFilter?.toString() ?? 'null',
'headers': () => _controller.headers,
Expand All @@ -67,6 +69,10 @@ class EnsembleImage extends StatefulWidget
Utils.optionalInt(height, min: 0, max: 2000),
'placeholderColor': (value) =>
_controller.placeholderColor = Utils.getColor(value),
'loadingWidget': (widget) => _controller.loadingWidget = widget,
'networkCacheManager': (value) =>
_controller.networkCacheManager =
value is BaseCacheManager ? value : null,
'fallback': (widget) => _controller.fallback = widget,
'onTap': (funcDefinition) => _controller.onTap =
EnsembleAction.from(funcDefinition, initiator: this),
Expand Down Expand Up @@ -94,6 +100,8 @@ class ImageController extends BoxController {
dynamic source;
BoxFit? fit;
Color? placeholderColor;
dynamic loadingWidget;
BaseCacheManager? networkCacheManager;
EnsembleAction? onTap;
String? onTapHaptic;
ColorFilterComposite? colorFilter;
Expand Down Expand Up @@ -122,13 +130,22 @@ class ImageState extends EWidgetState<EnsembleImage> {
Utils.getString(widget._controller.source?.trim(), fallback: '');
// use the placeholder for the initial state before binding kicks in
if (source.isEmpty) {
return const ColoredBoxPlaceholder();
return _buildEmptyPlaceholder(includeDimensions: false);
}

final Widget? loadingWidget = _buildLoadingWidget();
if (isSvg()) {
image = buildSvgImage(source, widget._controller.fit);
image = buildSvgImage(
source,
widget._controller.fit,
loadingWidget: loadingWidget,
);
} else {
image = buildNonSvgImage(source, widget._controller.fit);
image = buildNonSvgImage(
source,
widget._controller.fit,
loadingWidget: loadingWidget,
);
}
}

Expand Down Expand Up @@ -227,7 +244,33 @@ class ImageState extends EWidgetState<EnsembleImage> {
);
}

Widget buildNonSvgImage(String source, BoxFit? fit) {
Widget _buildLoadingPlaceholder({bool includeDimensions = true, Widget? loadingWidget}) {
return loadingWidget ??
ColoredBoxPlaceholder(
color: widget._controller.placeholderColor,
width: includeDimensions ? widget._controller.width?.toDouble() : null,
height:
includeDimensions ? widget._controller.height?.toDouble() : null,
);
}

Widget _buildEmptyPlaceholder({bool includeDimensions = true}) {
return ColoredBoxPlaceholder(
color: widget._controller.placeholderColor,
width: includeDimensions ? widget._controller.width?.toDouble() : null,
height: includeDimensions ? widget._controller.height?.toDouble() : null,
);
}

Widget? _buildLoadingWidget() {
if (widget._controller.loadingWidget == null || scopeManager == null) {
return null;
}
return scopeManager!
.buildWidgetFromDefinition(widget._controller.loadingWidget);
}

Widget buildNonSvgImage(String source, BoxFit? fit, {Widget? loadingWidget}) {
if (source.startsWith('https://') || source.startsWith('http://')) {
// If the asset is available locally, then use local path
String assetName = Utils.getAssetName(source);
Expand Down Expand Up @@ -258,15 +301,14 @@ class ImageState extends EWidgetState<EnsembleImage> {
// gigantic images won't run out of memory
memCacheWidth: cachedWidth,
memCacheHeight: cachedHeight,
cacheManager: EnsembleImageCacheManager.instanceFor(
allowRedirect: widget._controller.allowRedirect,
),
cacheManager: widget._controller.allowRedirect
? widget._controller.networkCacheManager ??
EnsembleImageCacheManager.instanceFor()
: EnsembleImageCacheManager.instanceFor(
allowRedirect: false,
),
errorWidget: (context, error, stacktrace) => errorFallback(),
placeholder: (context, url) => ColoredBoxPlaceholder(
color: widget._controller.placeholderColor,
width: widget._controller.width?.toDouble(),
height: widget._controller.height?.toDouble(),
),
placeholder: (context, url) => _buildLoadingPlaceholder(loadingWidget: loadingWidget),
httpHeaders: _evaluateHeaders(),
);
}
Expand All @@ -283,11 +325,7 @@ class ImageState extends EWidgetState<EnsembleImage> {
return cacheImage(widget.controller.source);
}
} else {
return ColoredBoxPlaceholder(
color: widget._controller.placeholderColor,
width: widget._controller.width?.toDouble(),
height: widget._controller.height?.toDouble(),
);
return _buildLoadingPlaceholder(loadingWidget: loadingWidget);
}
})
: cacheImage(widget._controller.source);
Expand Down Expand Up @@ -315,7 +353,7 @@ class ImageState extends EWidgetState<EnsembleImage> {
}
}

Widget buildSvgImage(String source, BoxFit? fit) {
Widget buildSvgImage(String source, BoxFit? fit, {Widget? loadingWidget}) {
// If the source starts with '<svg', treat it as inline SVG content
if (source.trim().toLowerCase().startsWith('<svg')) {
return SvgPicture.string(
Expand All @@ -341,11 +379,7 @@ class ImageState extends EWidgetState<EnsembleImage> {
width: widget._controller.width?.toDouble(),
height: widget._controller.height?.toDouble(),
fit: fit ?? BoxFit.contain,
placeholderBuilder: (_) => ColoredBoxPlaceholder(
color: widget._controller.placeholderColor,
width: widget._controller.width?.toDouble(),
height: widget._controller.height?.toDouble(),
),
placeholderBuilder: (_) => _buildLoadingPlaceholder(loadingWidget: loadingWidget),
errorBuilder: (_, __, ___) => errorFallback(),
httpClient: widget._controller.allowRedirect
? null
Expand Down
Loading