feat(channels): inline-кнопки под постами в каналах
This commit is contained in:
@@ -20,7 +20,5 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
+155
-8
@@ -1,62 +1,209 @@
|
||||
PODS:
|
||||
- app_links (7.0.0):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.9):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.9)
|
||||
- DKImagePickerController/PhotoGallery (4.3.9):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.9)
|
||||
- DKPhotoGallery (0.0.19):
|
||||
- DKPhotoGallery/Core (= 0.0.19)
|
||||
- DKPhotoGallery/Model (= 0.0.19)
|
||||
- DKPhotoGallery/Preview (= 0.0.19)
|
||||
- DKPhotoGallery/Resource (= 0.0.19)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_inappwebview_ios (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||
- OrderedSet (~> 6.0.3)
|
||||
- flutter_inappwebview_ios/Core (0.0.1):
|
||||
- Flutter
|
||||
- OrderedSet (~> 6.0.3)
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_secure_storage_darwin (10.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- flutter_timezone (0.0.1):
|
||||
- Flutter
|
||||
- flutter_webrtc (1.4.0):
|
||||
- Flutter
|
||||
- WebRTC-SDK (= 144.7559.09)
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- ogg_opus_player (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- open_filex (0.0.2):
|
||||
- Flutter
|
||||
- OrderedSet (6.0.3)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- photo_manager (3.9.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- record_ios (1.2.1):
|
||||
- Flutter
|
||||
- SDWebImage (5.21.7):
|
||||
- SDWebImage/Core (= 5.21.7)
|
||||
- SDWebImage/Core (5.21.7)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SwiftyGif (5.4.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- WebRTC-SDK (144.7559.09)
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
||||
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
|
||||
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- ogg_opus_player (from `.symlinks/plugins/ogg_opus_player/darwin`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/darwin`)
|
||||
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- OrderedSet
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- WebRTC-SDK
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_secure_storage_darwin:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
||||
flutter_timezone:
|
||||
:path: ".symlinks/plugins/flutter_timezone/ios"
|
||||
flutter_webrtc:
|
||||
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
||||
geolocator_apple:
|
||||
:path: ".symlinks/plugins/geolocator_apple/darwin"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
ogg_opus_player:
|
||||
:path: ".symlinks/plugins/ogg_opus_player/darwin"
|
||||
open_filex:
|
||||
:path: ".symlinks/plugins/open_filex/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/darwin"
|
||||
record_ios:
|
||||
:path: ".symlinks/plugins/record_ios/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_secure_storage_darwin: 557817588b80e60213cbecb573c45c76b788018d
|
||||
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_local_notifications: 643a3eda1ce1c0599413ca31672536d423dee214
|
||||
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||
flutter_webrtc: 3ee86aa10e0313c38c93fdb215dd86e77014f96a
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
media_kit_video: f3b0d035d89def15cfbbcf7dc2ae278f201e2f83
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
ogg_opus_player: 954784304f4e2722780018c4abf284e4f93cddf5
|
||||
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||
record_ios: 980fd386a97a35987d0fce3dfda4b26a38c90f4c
|
||||
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
WebRTC-SDK: e6006119cd730d6315d875e4a421b6cc8bb88833
|
||||
|
||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
||||
PODFILE CHECKSUM: 527e8cb59c3d722cc71f78b05ab13f192b9335d3
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
B1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */; };
|
||||
B1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */; };
|
||||
E835CC948F2F948DC3BBF405 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AE8CB2654009FE276CACA4B /* Pods_RunnerTests.framework */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -69,6 +70,7 @@
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9D7B0FFE63E0BD5D67D7758D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -84,6 +86,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
4B1A4BBCD56B3CE42AD0A480 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -111,6 +114,7 @@
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
@@ -194,6 +198,9 @@
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
@@ -219,6 +226,9 @@
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
@@ -731,6 +741,18 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,24 @@
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
|
||||
@@ -333,6 +333,8 @@ class ReplyInfo {
|
||||
return 'Ссылка';
|
||||
case AttachmentType.control:
|
||||
return '';
|
||||
case AttachmentType.inlineKeyboard:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
@@ -871,6 +873,28 @@ class MessagesModule {
|
||||
return response.isOk;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> sendButtonCallback({
|
||||
required int chatId,
|
||||
required String messageId,
|
||||
required String callbackId,
|
||||
String? payload,
|
||||
}) async {
|
||||
final mid = int.tryParse(messageId);
|
||||
if (mid == null) return null;
|
||||
|
||||
final request = {
|
||||
'chatId': chatId,
|
||||
'messageId': mid,
|
||||
'callbackId': callbackId,
|
||||
'payload': ?payload,
|
||||
};
|
||||
|
||||
final response = await _api.sendRequest(Opcode.msgSendCallback, request);
|
||||
if (!response.isOk) return null;
|
||||
final data = response.payload;
|
||||
return data is Map ? Map<String, dynamic>.from(data) : null;
|
||||
}
|
||||
|
||||
Future<TranscriptionResult> requestTranscription(
|
||||
int chatId,
|
||||
int messageId,
|
||||
|
||||
@@ -24,12 +24,18 @@ class WebAppModule {
|
||||
|
||||
WebAppModule(this._api);
|
||||
|
||||
Future<WebAppLaunch> fetchLaunch(int botId) async {
|
||||
Future<WebAppLaunch> fetchLaunch(
|
||||
int botId, {
|
||||
String? startParam,
|
||||
int? chatId,
|
||||
}) async {
|
||||
if (_api.state != SessionState.online) {
|
||||
throw const WebAppUnavailable('Нет соединения с сервером');
|
||||
}
|
||||
final packet = await _api.sendRequest(Opcode.webAppInitData, {
|
||||
'botId': botId,
|
||||
'startParam': ?startParam,
|
||||
'chatId': ?chatId,
|
||||
});
|
||||
if (!packet.isOk) {
|
||||
throw const WebAppUnavailable('Не удалось открыть мини-приложение');
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:komet/main.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../backend/modules/messages.dart';
|
||||
import '../screens/webapp/web_app_screen.dart';
|
||||
import '../../core/config/app_bubble_behavior.dart';
|
||||
import '../../core/config/app_bubble_shape.dart';
|
||||
import '../../core/config/komet_settings.dart';
|
||||
@@ -194,9 +195,20 @@ class MessageBubble extends StatelessWidget {
|
||||
return text;
|
||||
}
|
||||
|
||||
InlineKeyboardAttachment? get _inlineKeyboard {
|
||||
final attachments = message.attachments;
|
||||
if (attachments == null) return null;
|
||||
for (final a in attachments) {
|
||||
if (a is InlineKeyboardAttachment && !a.isEmpty) return a;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
MessageType _computeContentType() {
|
||||
if (message.isControl) return MessageType.control;
|
||||
final attachments = message.attachments;
|
||||
final attachments = message.attachments
|
||||
?.where((a) => a is! InlineKeyboardAttachment)
|
||||
.toList();
|
||||
if (attachments != null && attachments.isNotEmpty) {
|
||||
final first = attachments.first;
|
||||
if (first is ForwardedMessageAttachment) {
|
||||
@@ -433,6 +445,7 @@ class MessageBubble extends StatelessWidget {
|
||||
prevMessage?.senderId != message.senderId;
|
||||
|
||||
final maxBubbleWidth = MediaQuery.sizeOf(context).width * 0.75;
|
||||
final keyboard = _inlineKeyboard;
|
||||
final isVideoNote = _isVideoNote;
|
||||
final bubbleColor = isVideoNote
|
||||
? Colors.transparent
|
||||
@@ -553,6 +566,11 @@ class MessageBubble extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (keyboard != null)
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxBubbleWidth),
|
||||
child: _buildInlineKeyboard(context, cs, keyboard),
|
||||
),
|
||||
if (reactionsUnder) _reactionsBar(cs),
|
||||
],
|
||||
),
|
||||
@@ -562,6 +580,169 @@ class MessageBubble extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInlineKeyboard(
|
||||
BuildContext context,
|
||||
ColorScheme cs,
|
||||
InlineKeyboardAttachment keyboard,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (final row in keyboard.rows)
|
||||
if (row.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
for (var i = 0; i < row.length; i++) ...[
|
||||
if (i > 0) const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: _buildInlineKeyboardButton(
|
||||
context,
|
||||
cs,
|
||||
keyboard,
|
||||
row[i],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInlineKeyboardButton(
|
||||
BuildContext context,
|
||||
ColorScheme cs,
|
||||
InlineKeyboardAttachment keyboard,
|
||||
InlineKeyboardButton button,
|
||||
) {
|
||||
final trailingIcon = switch (button.type) {
|
||||
'LINK' => Symbols.open_in_new,
|
||||
'OPEN_APP' => Symbols.chevron_right,
|
||||
_ => null,
|
||||
};
|
||||
return Material(
|
||||
color: cs.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () => _onInlineButtonTap(context, keyboard, button),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
button.text,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: cs.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (trailingIcon != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(trailingIcon, size: 16, color: cs.primary),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onInlineButtonTap(
|
||||
BuildContext context,
|
||||
InlineKeyboardAttachment keyboard,
|
||||
InlineKeyboardButton button,
|
||||
) async {
|
||||
switch (button.type) {
|
||||
case 'LINK':
|
||||
final url = button.url;
|
||||
if (url != null && url.isNotEmpty) {
|
||||
await openExternalUrl(context, url);
|
||||
}
|
||||
return;
|
||||
case 'OPEN_APP':
|
||||
await _openMiniApp(context, button);
|
||||
return;
|
||||
default:
|
||||
final callbackId = keyboard.callbackId;
|
||||
if (callbackId == null || callbackId.isEmpty) {
|
||||
showCustomNotification(context, 'Кнопка не поддерживается');
|
||||
return;
|
||||
}
|
||||
final answer = await messagesModule.sendButtonCallback(
|
||||
chatId: message.chatId,
|
||||
messageId: message.id,
|
||||
callbackId: callbackId,
|
||||
payload: button.payload,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
final url = answer?['url']?.toString();
|
||||
if (url != null && url.isNotEmpty) {
|
||||
await openExternalUrl(context, url);
|
||||
return;
|
||||
}
|
||||
final text = answer?['text']?.toString();
|
||||
if (text != null && text.isNotEmpty) {
|
||||
showCustomNotification(context, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openMiniApp(
|
||||
BuildContext context,
|
||||
InlineKeyboardButton button,
|
||||
) async {
|
||||
final deeplink = button.webApp != null
|
||||
? Uri.tryParse(button.webApp!)
|
||||
: null;
|
||||
final startParam =
|
||||
button.payload ??
|
||||
deeplink?.queryParameters['startapp'] ??
|
||||
deeplink?.queryParameters['startApp'];
|
||||
final chatId =
|
||||
int.tryParse(deeplink?.queryParameters['chat_id'] ?? '') ??
|
||||
message.chatId;
|
||||
final botId = button.contactId;
|
||||
|
||||
if (botId == null) {
|
||||
showCustomNotification(context, 'Не удалось открыть приложение');
|
||||
return;
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[inline-kb] OPEN_APP botId=$botId chatId=$chatId startParam=$startParam',
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => WebAppScreen(
|
||||
title: button.text,
|
||||
loader: () => webAppModule.fetchLaunch(
|
||||
botId,
|
||||
startParam: startParam,
|
||||
chatId: chatId,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map? _resolveReactionInfo() {
|
||||
if (reactionsListenable != null) {
|
||||
final v = reactionsListenable!.value;
|
||||
|
||||
@@ -12,6 +12,7 @@ enum AttachmentType {
|
||||
poll,
|
||||
share,
|
||||
call,
|
||||
inlineKeyboard,
|
||||
}
|
||||
|
||||
String? decodeAttachPreview(dynamic raw) {
|
||||
@@ -64,7 +65,7 @@ abstract class MessageAttachment {
|
||||
case 'SHARE':
|
||||
return ShareAttachment.fromMap(map);
|
||||
case 'INLINE_KEYBOARD':
|
||||
return UnknownAttachment(map);
|
||||
return InlineKeyboardAttachment.fromMap(map);
|
||||
default:
|
||||
return UnknownAttachment(map);
|
||||
}
|
||||
@@ -580,6 +581,93 @@ class ShareAttachment extends MessageAttachment {
|
||||
};
|
||||
}
|
||||
|
||||
class InlineKeyboardButton {
|
||||
final String type;
|
||||
final String text;
|
||||
final String? url;
|
||||
final String? webApp;
|
||||
final int? contactId;
|
||||
final String? payload;
|
||||
|
||||
const InlineKeyboardButton({
|
||||
required this.type,
|
||||
required this.text,
|
||||
this.url,
|
||||
this.webApp,
|
||||
this.contactId,
|
||||
this.payload,
|
||||
});
|
||||
|
||||
factory InlineKeyboardButton.fromMap(Map<String, dynamic> map) {
|
||||
return InlineKeyboardButton(
|
||||
type: (map['type'] as String? ?? '').toUpperCase(),
|
||||
text: map['text']?.toString() ?? '',
|
||||
url: map['url']?.toString(),
|
||||
webApp: map['webApp']?.toString(),
|
||||
contactId: map['contactId'] is int
|
||||
? map['contactId'] as int
|
||||
: int.tryParse(map['contactId']?.toString() ?? ''),
|
||||
payload: map['payload']?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
'type': type,
|
||||
'text': text,
|
||||
if (url != null) 'url': url,
|
||||
if (webApp != null) 'webApp': webApp,
|
||||
if (contactId != null) 'contactId': contactId,
|
||||
if (payload != null) 'payload': payload,
|
||||
};
|
||||
}
|
||||
|
||||
class InlineKeyboardAttachment extends MessageAttachment {
|
||||
final String? callbackId;
|
||||
final List<List<InlineKeyboardButton>> rows;
|
||||
|
||||
const InlineKeyboardAttachment({
|
||||
this.callbackId,
|
||||
required this.rows,
|
||||
}) : super(type: AttachmentType.inlineKeyboard);
|
||||
|
||||
bool get isEmpty => rows.every((row) => row.isEmpty);
|
||||
|
||||
factory InlineKeyboardAttachment.fromMap(Map<String, dynamic> map) {
|
||||
final keyboard = map['keyboard'];
|
||||
final rawRows = keyboard is Map ? keyboard['buttons'] as List? : null;
|
||||
final rows = <List<InlineKeyboardButton>>[];
|
||||
if (rawRows != null) {
|
||||
for (final row in rawRows) {
|
||||
if (row is! List) continue;
|
||||
rows.add(
|
||||
row
|
||||
.whereType<Map>()
|
||||
.map(
|
||||
(b) =>
|
||||
InlineKeyboardButton.fromMap(Map<String, dynamic>.from(b)),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return InlineKeyboardAttachment(
|
||||
callbackId: map['callbackId']?.toString(),
|
||||
rows: rows,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => {
|
||||
'_type': 'INLINE_KEYBOARD',
|
||||
if (callbackId != null) 'callbackId': callbackId,
|
||||
'keyboard': {
|
||||
'buttons': rows
|
||||
.map((row) => row.map((b) => b.toMap()).toList())
|
||||
.toList(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class ForwardedMessageAttachment extends MessageAttachment {
|
||||
final int originalSenderId;
|
||||
final String? originalSenderName;
|
||||
|
||||
Reference in New Issue
Block a user