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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sama_sdk/api/api.dart' hide DeleteMessagesStatus;
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
Expand All @@ -7,6 +8,7 @@ import '../../../db/models/models.dart';
import '../../../shared/ui/colors.dart';
import '../../../shared/utils/date_utils.dart';
import '../../../shared/utils/list_utils.dart';
import '../../../shared/utils/regexp_utils.dart';
import '../../../shared/utils/screen_factor.dart';
import '../../../shared/utils/string_utils.dart';
import '../bloc/conversation_bloc.dart';
Expand Down Expand Up @@ -491,6 +493,75 @@ class MessageItem extends StatelessWidget {
);
});
}),
if ((message.body != null) ||
message.attachments.firstOrNull != null)
FocusedPopupMenuItem(
leadingIcon: const Icon(Icons.copy_outlined),
title: const Text('Copy'),
onPressed: () {
var text = message.body;
var link = text?.firstUrl;
var email = text?.firstEmail;
var phone = text?.firstPhone;
var url = message.attachments.firstOrNull?.url;

FocusedPopupMenu(
menuItems: <FocusedPopupMenuItem>[
if (link?.isNotEmpty ?? false)
FocusedPopupMenuItem(
title: const Text('Copy URL'),
onPressed: () {
Clipboard.setData(
ClipboardData(text: link!),
);
}),
if (email?.isNotEmpty ?? false)
FocusedPopupMenuItem(
title: const Text('Copy email'),
onPressed: () {
Clipboard.setData(
ClipboardData(text: email!),
);
}),
if (phone?.isNotEmpty ?? false)
FocusedPopupMenuItem(
title: const Text('Copy phone'),
onPressed: () {
Clipboard.setData(
ClipboardData(text: phone!),
);
}),
if (url?.isNotEmpty ?? false)
FocusedPopupMenuItem(
title:
const Text('Copy attachment'),
onPressed: () {
Clipboard.setData(
ClipboardData(text: url!),
);
}),
if (text?.isNotEmpty ?? false)
FocusedPopupMenuItem(
title: const Text('Copy message'),
onPressed: () {
Clipboard.setData(
ClipboardData(text: text!),
);
}),
],
context: context,
child: MultiBlocProvider(providers: [
BlocProvider.value(
value: BlocProvider.of<
MediaAttachmentBloc>(
context)),
BlocProvider.value(
value: BlocProvider.of<
ConversationBloc>(context)),
], child: this),
stickToRight: message.isOwn)
.show();
}),
FocusedPopupMenuItem(
leadingIcon: const Icon(Icons.check_circle_outline),
title: const Text('Select'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ class FocusedMenuDetails extends StatelessWidget {
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;

final itemBottom = childOffset.dy + childSize.height;
final bottomOffset = screenHeight - itemBottom;

final menuHeight = menuItems.length * menuItemHeight;
final childPaddingDy = menuHeight + topPaddingHeight;

final needToMove = menuHeight +
childSize.height +
topPaddingHeight -
childOffset.dy.abs() >
size.height;
final needToMove = menuHeight + topPaddingHeight > childOffset.dy &&
menuHeight > bottomOffset;

final leftOffset = stickToRight
? childOffset.dx -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ class TextMessage extends StatelessWidget {

List<InlineSpan> linkify(String text, [bool preview = true]) {
final List<InlineSpan> list = <InlineSpan>[];
final RegExp linkRegExp =
RegExp('($urlPattern)|($emailPattern)|($phonePattern)');
final RegExpMatch? match = linkRegExp.firstMatch(text);
final RegExpMatch? match = RegPatterns.any.firstMatch(text);

if (match == null) {
list.add(TextSpan(text: text));
Expand All @@ -105,16 +103,16 @@ class TextMessage extends StatelessWidget {
}

final String linkText = match.group(0)!;
if (linkText.contains(RegExp(urlPattern))) {
if (linkText.contains(RegPatterns.url)) {
if (preview) {
preview = false;
list.add(buildLinkPreviewComponent(linkText, linkText));
} else {
list.add(buildTextComponent(linkText, linkText));
}
} else if (linkText.contains(RegExp(emailPattern))) {
} else if (linkText.contains(RegPatterns.email)) {
list.add(buildTextComponent(linkText, 'mailto:$linkText'));
} else if (linkText.contains(RegExp(phonePattern))) {
} else if (linkText.contains(RegPatterns.phone)) {
list.add(buildTextComponent(linkText, 'tel:$linkText'));
} else {
throw 'Unexpected match: $linkText';
Expand Down
28 changes: 25 additions & 3 deletions sama_chat_client/lib/src/shared/utils/regexp_utils.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
const String urlPattern = 'https?://\\S+';
const String emailPattern = r'\S+@\S+';
const String phonePattern = r'[\d-]{9,}';
class RegPatterns {
static final RegExp url = RegExp(r'https?://\S+');
static final RegExp email = RegExp(r'\S+@\S+');
static final RegExp phone = RegExp(r'\+?\d[\d\s-]{8,}\d');

static final RegExp any = RegExp(
'(?:${url.pattern})|'
'(?:${email.pattern})|'
'(?:${phone.pattern})',
);
}

extension RegPatternsExtension on String {
String? _firstMatch(RegExp pattern) => pattern.firstMatch(this)?.group(0);

String? get firstUrl => _firstMatch(RegPatterns.url);

String? get firstEmail => _firstMatch(RegPatterns.email);

String? get firstPhone => _firstMatch(RegPatterns.phone);
}

List<String> matches(String text, RegExp regExp) {
return regExp.allMatches(text).map((m) => m.group(0)!).toList();
}