feat: payload для /start в ботах.

This commit is contained in:
Jganenokk
2026-07-30 18:39:31 +07:00
parent 8f42692708
commit f6de41f11d
13 changed files with 483 additions and 38 deletions
@@ -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,
+6 -2
View File
@@ -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);
+82 -3
View File
@@ -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(
+1
View File
@@ -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(