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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## [Unreleased]

* **Feat**: [454](https://github.com/SimformSolutionsPvtLtd/chatview/pull/454) :sparkles: Add configurable image fit option for AdaptiveImage widget
* **Feat**: [457](https://github.com/SimformSolutionsPvtLtd/chatview/pull/457) :sparkles: Add errorWidget support to ImageMessageConfiguration

## 3.1.0

Expand Down
16 changes: 16 additions & 0 deletions lib/src/models/config_models/image_message_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@
import 'package:chatview_utils/chatview_utils.dart';
import 'package:flutter/material.dart';

/// Builds the widget shown when an image message fails to load.
///
/// [imageUrl] is the image source (network url, file path, or data uri) and
/// [error] is the object thrown while loading it.
typedef ImageMessageErrorWidgetBuilder = Widget Function(
BuildContext context,
String imageUrl,
Object error,
);

class ImageMessageConfiguration {
const ImageMessageConfiguration({
this.hideShareIcon = false,
Expand All @@ -34,6 +44,7 @@ class ImageMessageConfiguration {
this.margin,
this.borderRadius,
this.fit = BoxFit.cover,
this.errorBuilder,
});

/// Provides configuration of share button while image message is appeared.
Expand Down Expand Up @@ -68,6 +79,11 @@ class ImageMessageConfiguration {
/// Defaults to [BoxFit.cover] (fills the bubble, center-cropped). Use
/// [BoxFit.contain] to show the whole image without cropping.
final BoxFit fit;

/// Builds a custom widget shown when an image fails to load, with access to
/// the failing [imageUrl] and the [error]. Replaces the default broken-image
/// placeholder when set.
final ImageMessageErrorWidgetBuilder? errorBuilder;
}

class ShareIconConfiguration {
Expand Down
1 change: 1 addition & 0 deletions lib/src/utils/constants/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const String imageUrlRegExpression =
r'(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png|jpeg)';
const String dateFormat = "yyyy-MM-dd";
const String couldNotLaunch = "Could not launch";
const String couldNotLoadImage = "Couldn't load image";
const String heart = "\u{2764}";
const String faceWithTears = "\u{1F602}";
const String disappointedFace = "\u{1F625}";
Expand Down
41 changes: 36 additions & 5 deletions lib/src/widgets/adaptive_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'dart:io';

import 'package:cached_network_image/cached_network_image.dart';
import 'package:chatview/src/extensions/extensions.dart';
import 'package:chatview/src/models/config_models/image_message_configuration.dart';
import 'package:chatview/src/utils/constants/constants.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

Expand All @@ -11,13 +13,18 @@ class AdaptiveImage extends StatelessWidget {
super.key,
required this.imageUrl,
this.fit = BoxFit.cover,
this.errorBuilder,
});

final String imageUrl;

/// How the image is inscribed into its box. See [BoxFit].
final BoxFit fit;

/// Builds a custom error widget with access to the failing [imageUrl] and
/// [error]. Falls back to a built-in placeholder when null.
final ImageMessageErrorWidgetBuilder? errorBuilder;

@override
Widget build(BuildContext context) {
if (imageUrl.isUrl) {
Expand All @@ -31,9 +38,7 @@ class AdaptiveImage extends StatelessWidget {
progressIndicatorBuilder: (context, url, progress) => Center(
child: CircularProgressIndicator(value: progress.progress),
),
errorWidget: (context, url, error) => const Center(
child: Icon(Icons.error_outline, size: 18),
),
errorWidget: (context, _, error) => _buildError(context, error),
);
} else if (imageUrl.fromMemory) {
return Image.memory(
Expand All @@ -42,14 +47,40 @@ class AdaptiveImage extends StatelessWidget {
// Avoid a flicker to a blank frame while the next decode happens on
// rebuild (e.g. during scrolling).
gaplessPlayback: true,
errorBuilder: (context, error, stackTrace) =>
_buildError(context, error),
);
} else {
return kIsWeb
? Image.network(imageUrl, fit: fit)
: Image.file(File(imageUrl), fit: fit);
? Image.network(
imageUrl,
fit: fit,
errorBuilder: (context, error, _) => _buildError(context, error),
)
: Image.file(
File(imageUrl),
fit: fit,
errorBuilder: (context, error, _) => _buildError(context, error),
);
}
}

Widget _buildError(BuildContext context, Object error) =>
errorBuilder?.call(context, imageUrl, error) ??
const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 30, color: Colors.white70),
SizedBox(height: 8),
Text(
couldNotLoadImage,
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
);

/// Decodes the base64 payload of a `data:image` URI.
Uint8List _decodeBase64(String value) {
final startIndex = value.indexOf('base64,');
Expand Down
1 change: 1 addition & 0 deletions lib/src/widgets/image_message_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class ImageMessageView extends StatelessWidget {
child: AdaptiveImage(
imageUrl: imageUrl,
fit: imageMessageConfig?.fit ?? BoxFit.cover,
errorBuilder: imageMessageConfig?.errorBuilder,
),
),
),
Expand Down
Loading