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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## [3.0.0]

* **Feat**: [374](https://github.com/SimformSolutionsPvtLtd/chatview/issues/374)
Add support for displaying and selecting text and links in a single message view.
* **Breaking**: [411](https://github.com/SimformSolutionsPvtLtd/chatview/pull/411)
Update the example iOS deployment target to 13, as the example depends on `audiowaveform`, which requires iOS 13 or later.
* **Feat**: [401](https://github.com/SimformSolutionsPvtLtd/chatview/pull/401)
Expand Down
6 changes: 6 additions & 0 deletions lib/src/extensions/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ extension ValidateString on String {

bool get isUrl => Uri.tryParse(this)?.isAbsolute ?? false;

/// Regular expression pattern to match URLs.
static final _urlRegex = RegExp(urlRegex, caseSensitive: false);

/// Extracts the first URL found in the string.
String? get extractedUrl => _urlRegex.firstMatch(this)?.group(0);

Widget getUserProfilePicture({
required ChatUser? Function(String) getChatUser,
double? profileCircleRadius,
Expand Down
5 changes: 5 additions & 0 deletions lib/src/utils/constants/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ applicationDateFormatter(DateTime inputTime) {
}
}

/// Regular expression to identify URLs in a text.
const String urlRegex =
r'((https?://)?(www\.)?[a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b'
r'([-a-zA-Z0-9@:%_\+.~#?&//=]*))';

/// Default widget that appears on receipts at [MessageStatus.pending] when a message
/// is not sent or at the pending state. A custom implementation can have different
/// widgets for different states.
Expand Down
25 changes: 15 additions & 10 deletions lib/src/widgets/link_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,36 @@ import '../utils/constants/constants.dart';
class LinkPreview extends StatelessWidget {
const LinkPreview({
Key? key,
required this.url,
required this.textMessage,
required this.extractedUrl,
this.linkPreviewConfig,
}) : super(key: key);

/// Provides the whole text message to show.
final String textMessage;

/// Provides url which is passed in message.
final String url;
final String extractedUrl;

/// Provides configuration of chat bubble appearance when link/URL is passed
/// in message.
final LinkPreviewConfiguration? linkPreviewConfig;

@override
Widget build(BuildContext context) {
final isImageUrl = extractedUrl.isImageUrl;
return Padding(
padding: linkPreviewConfig?.padding ??
const EdgeInsets.symmetric(horizontal: 6, vertical: verticalPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!url.isImageUrl &&
if (!isImageUrl &&
!(context.chatBubbleConfig?.disableLinkPreview ?? false)) ...{
Padding(
padding: const EdgeInsets.symmetric(vertical: verticalPadding),
child: AnyLinkPreview(
link: url,
link: extractedUrl,
removeElevation: true,
errorBody: linkPreviewConfig?.errorBody,
proxyUrl: linkPreviewConfig?.proxyUrl,
Expand All @@ -77,13 +82,13 @@ class LinkPreview extends StatelessWidget {
titleStyle: linkPreviewConfig?.titleStyle,
),
),
} else if (url.isImageUrl) ...{
} else if (isImageUrl) ...{
Padding(
padding: const EdgeInsets.symmetric(vertical: verticalPadding),
child: InkWell(
onTap: _onLinkTap,
child: Image.network(
url,
extractedUrl,
height: 120,
width: double.infinity,
fit: BoxFit.fitWidth,
Expand All @@ -95,7 +100,7 @@ class LinkPreview extends StatelessWidget {
InkWell(
onTap: _onLinkTap,
child: Text(
url,
textMessage,
style: linkPreviewConfig?.linkStyle ??
const TextStyle(
color: Colors.white,
Expand All @@ -109,15 +114,15 @@ class LinkPreview extends StatelessWidget {
}

void _onLinkTap() {
if (linkPreviewConfig?.onUrlDetect != null) {
linkPreviewConfig?.onUrlDetect!(url);
if (linkPreviewConfig?.onUrlDetect case final onUrlDetect?) {
onUrlDetect(extractedUrl);
} else {
_launchURL();
}
}

void _launchURL() async {
final parsedUrl = Uri.parse(url);
final parsedUrl = Uri.parse(extractedUrl);
await canLaunchUrl(parsedUrl)
? await launchUrl(parsedUrl)
: throw couldNotLaunch;
Expand Down
38 changes: 20 additions & 18 deletions lib/src/widgets/text_message_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,21 @@ class TextMessageView extends StatelessWidget {
final textSelectionConfig = isMessageBySender
? outgoingChatBubbleConfig?.textSelectionConfig
: inComingChatBubbleConfig?.textSelectionConfig;
final baseWidget = Text(
textMessage,
style: _textStyle ??
textTheme.bodyMedium!.copyWith(
color: Colors.white,
fontSize: 16,
),
);
final extractedUrl = textMessage.extractedUrl;
final baseWidget = extractedUrl != null
? LinkPreview(
linkPreviewConfig: _linkPreviewConfig,
textMessage: textMessage,
extractedUrl: extractedUrl,
)
: Text(
textMessage,
style: _textStyle ??
textTheme.bodyMedium!.copyWith(
color: Colors.white,
fontSize: 16,
),
);
return Stack(
clipBehavior: Clip.none,
children: [
Expand All @@ -106,17 +113,12 @@ class TextMessageView extends StatelessWidget {
border: border,
borderRadius: _borderRadius(textMessage),
),
child: textMessage.isUrl
? LinkPreview(
linkPreviewConfig: _linkPreviewConfig,
url: textMessage,
child: isSelectable
? CustomSelectionArea(
config: textSelectionConfig,
child: baseWidget,
)
: isSelectable
? CustomSelectionArea(
config: textSelectionConfig,
child: baseWidget,
)
: baseWidget,
: baseWidget,
),
if (message.reaction.reactions.isNotEmpty)
ReactionWidget(
Expand Down