feat: payload для /start в ботах.
This commit is contained in:
@@ -92,7 +92,7 @@ class _FormattedMessageTextState extends State<FormattedMessageText> {
|
||||
if (hasExplicitLink) return ranges;
|
||||
for (final match in linkPattern.allMatches(widget.text)) {
|
||||
final raw = match.group(0)!;
|
||||
final target = raw.startsWith('www.') ? 'https://$raw' : raw;
|
||||
final target = linkTarget(raw);
|
||||
ranges.add(
|
||||
FormatRange(
|
||||
format: TextFormat.link,
|
||||
|
||||
@@ -4,10 +4,14 @@ import 'package:flutter/material.dart';
|
||||
import '../../core/utils/link_opener.dart';
|
||||
|
||||
final RegExp linkPattern = RegExp(
|
||||
r'(https?://[^\s<>]+|www\.[^\s<>]+)',
|
||||
r'(https?://[^\s<>]+'
|
||||
r'|www\.[^\s<>]+'
|
||||
r'|(?<![\w.@/-])max\.ru(?![\w.-])(?:/[^\s<>]*)?)',
|
||||
caseSensitive: false,
|
||||
);
|
||||
|
||||
String linkTarget(String raw) => raw.contains('://') ? raw : 'https://$raw';
|
||||
|
||||
class LinkText extends StatefulWidget {
|
||||
final String text;
|
||||
final TextStyle style;
|
||||
@@ -46,7 +50,7 @@ class _LinkTextState extends State<LinkText> {
|
||||
spans.add(TextSpan(text: widget.text.substring(cursor, match.start)));
|
||||
}
|
||||
final url = match.group(0)!;
|
||||
final target = url.startsWith('www.') ? 'https://$url' : url;
|
||||
final target = linkTarget(url);
|
||||
final recognizer = TapGestureRecognizer()
|
||||
..onTap = () => openExternalUrl(context, target);
|
||||
_recognizers.add(recognizer);
|
||||
|
||||
@@ -33,7 +33,7 @@ Future<bool> tryHandleMaxLink(BuildContext context, String url) async {
|
||||
return _openStickerSet(context, link.url);
|
||||
}
|
||||
|
||||
final resolved = await LinkModule.resolve(api, link.url);
|
||||
final resolved = await _resolve(link);
|
||||
if (!context.mounted) return true;
|
||||
|
||||
switch (resolved) {
|
||||
@@ -43,7 +43,7 @@ Future<bool> tryHandleMaxLink(BuildContext context, String url) async {
|
||||
showCustomNotification(context, message);
|
||||
return true;
|
||||
case ResolvedUser(:final contact):
|
||||
_openContact(context, contact);
|
||||
await _openContact(context, link, contact);
|
||||
return true;
|
||||
case ResolvedChat():
|
||||
await _openResolvedChat(context, link, resolved);
|
||||
@@ -51,6 +51,13 @@ Future<bool> tryHandleMaxLink(BuildContext context, String url) async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ResolvedLink?> _resolve(MaxLink link) async {
|
||||
final resolved = await LinkModule.resolve(api, link.url);
|
||||
if (link.startPayload == null || link.baseUrl == link.url) return resolved;
|
||||
if (resolved is ResolvedChat || resolved is ResolvedUser) return resolved;
|
||||
return LinkModule.resolve(api, link.baseUrl);
|
||||
}
|
||||
|
||||
Future<bool> _openStickerSet(BuildContext context, String url) async {
|
||||
final path = url
|
||||
.replaceFirst(
|
||||
@@ -71,12 +78,24 @@ Future<bool> _openStickerSet(BuildContext context, String url) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
void _openContact(BuildContext context, Map<dynamic, dynamic> contact) {
|
||||
Future<void> _openContact(
|
||||
BuildContext context,
|
||||
MaxLink link,
|
||||
Map<dynamic, dynamic> contact,
|
||||
) async {
|
||||
final id = contact['id'];
|
||||
if (id is! int) {
|
||||
showCustomNotification(context, 'Не удалось открыть профиль');
|
||||
return;
|
||||
}
|
||||
|
||||
final startPayload = link.startPayload;
|
||||
if (startPayload != null &&
|
||||
await _startBotDialog(context, id, contact, startPayload)) {
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
|
||||
unawaited(
|
||||
openContactDialogProfile(
|
||||
context,
|
||||
@@ -87,6 +106,53 @@ void _openContact(BuildContext context, Map<dynamic, dynamic> contact) {
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _startBotDialog(
|
||||
BuildContext context,
|
||||
int botId,
|
||||
Map<dynamic, dynamic> contact,
|
||||
String startPayload,
|
||||
) async {
|
||||
final profile = await AppDatabase.loadActiveProfile();
|
||||
final myId = profile?.id ?? 0;
|
||||
if (myId == 0) return false;
|
||||
|
||||
final chatId =
|
||||
await AppDatabase.findDialogChatByParticipant(myId, botId) ??
|
||||
(myId ^ botId);
|
||||
if (chatId <= 0 || !context.mounted) return false;
|
||||
|
||||
_openChatAndStartBot(
|
||||
context,
|
||||
chatId: chatId,
|
||||
name: _contactName(contact),
|
||||
imageUrl: (contact['baseUrl'] as String?) ?? '',
|
||||
chatType: 'DIALOG',
|
||||
startPayload: startPayload,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
void _openChatAndStartBot(
|
||||
BuildContext context, {
|
||||
required int chatId,
|
||||
required String name,
|
||||
required String imageUrl,
|
||||
required String chatType,
|
||||
required String startPayload,
|
||||
}) {
|
||||
if (ChatScreen.startBotInVisibleChat(chatId, startPayload)) return;
|
||||
pushSwipeable(
|
||||
context,
|
||||
(_) => ChatScreen(
|
||||
chatId: chatId,
|
||||
name: name,
|
||||
imageUrl: imageUrl,
|
||||
chatType: chatType,
|
||||
botStartPayload: startPayload,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openResolvedChat(
|
||||
BuildContext context,
|
||||
MaxLink link,
|
||||
@@ -131,6 +197,19 @@ Future<void> _openResolvedChat(
|
||||
if (!context.mounted) return;
|
||||
}
|
||||
|
||||
final startPayload = link.startPayload;
|
||||
if (startPayload != null && type == 'DIALOG') {
|
||||
_openChatAndStartBot(
|
||||
context,
|
||||
chatId: id,
|
||||
name: title,
|
||||
imageUrl: icon,
|
||||
chatType: type,
|
||||
startPayload: startPayload,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
pushSwipeable(
|
||||
context,
|
||||
(_) => ChatScreen(
|
||||
|
||||
@@ -752,6 +752,7 @@ class MessageBubble extends StatelessWidget {
|
||||
final contentType = _contentType;
|
||||
|
||||
if (message.isControl) {
|
||||
if (message.isBotStartMarker) return const SizedBox.shrink();
|
||||
const controlShape = BubbleShape.singleMiddle;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
|
||||
Reference in New Issue
Block a user