
Немного теории
Для использования готовых нативных библиотек в MAUI нам предлагают механизмы Binding native library и Native Library Interop. Binding native library работает с собранными библиотеками (aar или jar для Android, XCFrameworks для iOS/Macos), Native Library Interop (или slim bindings) - это способ добавлять в проект ссылки на нативные проекты (Xcode или gradle), в процессе компиляции проекта обещают автоматическую соборку нативной библиотеки и генерацию обёртки для неё. Кроме того есть еще старый добрый P/Invoke, но это совсем другая история.
Чего не хватает?
В списке доступных SDK RuStore много чего на все случаи жизни. Тут нам и пуши, встроенные покупки, работа с обновлениями, отзывами и прочие штуки. Кроме того тут не указаны sdk для сторонних сервисов, которые можно подключить к приложению, например, tracer от ОК.
Теоретически к приложению на MAUI для андроид можно всё это дело подключить.
Основной минус - у вас должен быть аккаунт RuStore.
Еще немного теории.
Раньше, до .Net 9, для добавления нативных зависимостей Android (*.jar или *.aar) нужно было скачать aar, подложить его в проект или куда-то рядом (с помощью IDE или руками).
Если добавляли руками, нужно было указать ссылку на файл.
<ItemGroup> <AndroidLibrary Update="aar/my_lib.aar"/> </ItemGroup>
С релизом .Net 9 появилась опция AndroidMavenLibrary. Как вы,наверняка поняли из названия - с помощью неё можно задать ссылку на библиотеку в удалённом репозитории Maven. Очень важно что у этой опции есть свойство Repository, с помощью которого можно указать Url для cкачивания aar. Описание по ссылке выше.
Делаем получение пушей из RuStore
Нам нужен солюшен из двух проектов:
1. Андроид биндинг (в VS Code этот шаблон называется "привязка библиотеки Java для Android"), в котором будет генерироваться обёртка для Maui
2. Проект для тестового приложения, со ссылкой на первый проект.
Открываем файл первого проекта как текст и вставляем в корневой элемент этот кусок
<ItemGroup> <AndroidMavenLibrary Include="ru.rustore.sdk:pushclient" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" /> </ItemGroup>
Должно получиться примерно так:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net9.0-android</TargetFramework> <SupportedOSPlatformVersion>24</SupportedOSPlatformVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <IsTrimmable>true</IsTrimmable> </PropertyGroup> <ItemGroup> <AndroidMavenLibrary Include="ru.rustore.sdk:pushclient" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" /> </ItemGroup> </Project>
Так мы добавили ссылку на библиотеку pushclient версии 7.0.0 и указали источник для скачивания, который указан в разделе "Подключение в проект" официальной документации RuStore Push SDK.
Жмём собрать проект в IDE или dotnet build в консоли и получаем кучу ошибок.

Тут мы видим что вместе с aar скачался *.pom файл с описанием зависимостей и каким-то образом их надо добавить в проект. Пока что автоматическое скачивание зависимостей не допилили, да.
Если вглядеться, то в описании некоторых ошибок пишется "Корпорация Майкрософт поддерживает пакет NuGet "Square.OkHttp3", который может выполнить эту зависимость." Это значит что можно добавить NuGet c обёрткой наподобие той что мы пытаемся сделать, а можно добавить ссылку с помощью AndroidMavenLibrary, по аналогии с основным пакетом. Можно ошибки закинуть в нейронку, но тут важно следить за версиями библиотек в ответе. Можно залипнуть на несколько дней просто из-за того что в ответе нейронки будет другая версия библиотеки. В итоге у меня получился вот такой-вот список зависимостей.
Скрытый текст
<ItemGroup> <!-- RuStore SDK из собственного репозитория --> <AndroidMavenLibrary Include="ru.rustore.sdk:pushclient" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" /> <AndroidMavenLibrary Include="ru.rustore.sdk:core" Version="8.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" /> <AndroidMavenLibrary Include="ru.rustore.sdk:push-core" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false" /> <AndroidMavenLibrary Include="ru.rustore.sdk:push-core-remote-config" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false" /> <AndroidMavenLibrary Include="ru.rustore.sdk:push-common" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven"/> <AndroidMavenLibrary Include="ru.rustore.sdk:push-core-network" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false" /> <AndroidMavenLibrary Include="ru.rustore.sdk:coreui" Version="8.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false" /> <AndroidMavenLibrary Include="ru.rustore.sdk:metrics" Version="8.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false" /> <AndroidMavenLibrary Include="ru.rustore.sdk:reactive" Version="8.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false"/> <!-- HMS из Huawei репозитория --> <AndroidMavenLibrary Include="com.huawei.hms:push" Version="6.12.0.300" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:opendevice" Version="6.12.0.300" Repository="https://developer.huawei.com/repo/" Bind="false" JavaArtifact="com.huawei.android.hms:security-encrypt:1.2.0.307;com.huawei.android.hms:security-base:1.2.0.307;com.huawei.hms:base:6.11.0.302;javax.inject:javax.inject:1" /> <AndroidMavenLibrary Include="com.huawei.android.hms:security-encrypt" Version="1.2.0.307" Repository="https://developer.huawei.com/repo/" Bind="false" JavaArtifact="com.huawei.android.hms:security-base:1.2.0.307;com.huawei.hms:base:6.11.0.302;javax.inject:javax.inject:1" /> <AndroidMavenLibrary Include="com.huawei.android.hms:security-base" Version="1.2.0.307" Repository="https://developer.huawei.com/repo/" Bind="false" JavaArtifact="com.huawei.hms:base:6.11.0.302;javax.inject:javax.inject:1" /> <AndroidMavenLibrary Include="com.huawei.hms:base" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:baselegacyapi" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:stats" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:ui" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:device" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:log" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hmf:tasks" Version="1.5.2.206" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:availableupdate" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:hatool" Version="6.11.0.302" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:network-grs" Version="6.0.11.300" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.agconnect:agconnect-core" Version="1.8.1.300" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.android.hms:security-ssl" Version="1.2.0.307" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:network-common" Version="6.0.11.300" Repository="https://developer.huawei.com/repo/" Bind="false" /> <AndroidMavenLibrary Include="com.huawei.hms:network-framework-compat" Version="6.0.11.300" Repository="https://developer.huawei.com/repo/" Bind="false" /> <!-- RuStore SDK внешние зависимости --> <AndroidMavenLibrary Include="ru.ok.tracer:tracer-base" Version="1.1.1" Bind="false" /> <AndroidMavenLibrary Include="ru.ok.tracer:tracer-manifest" Version="1.1.1" Bind="false" /> <AndroidMavenLibrary Include="ru.ok.tracer:tracer-lite-commons" Version="1.1.1" Bind="false" /> <AndroidMavenLibrary Include="ru.ok.tracer:tracer-lite-crash-report" Version="1.1.1" Bind="false" /> </ItemGroup> <ItemGroup> <PackageReference Include="Square.OkHttp3" Version="4.10.0" /> <PackageReference Include="Square.OkIO.JVM" Version="3.0.0"/> <!-- Kotlin runtime --> <PackageReference Include="Xamarin.AndroidX.SavedState" Version="1.3.3" /> <PackageReference Include="Xamarin.AndroidX.SavedState.SavedState.Android" Version="1.3.3" /> <PackageReference Include="Xamarin.AndroidX.SavedState.SavedState.Ktx" Version="1.3.3" /> <PackageReference Include="Xamarin.Kotlin.StdLib" Version="2.2.20" /> <PackageReference Include="Xamarin.Kotlin.StdLib.Common" Version="2.0.21.5" /> <PackageReference Include="Xamarin.Kotlin.StdLib.Jdk8" Version="2.2.20" /> <PackageReference Include="Xamarin.KotlinX.Coroutines.Core" Version="1.10.2.1" /> <PackageReference Include="Xamarin.KotlinX.Coroutines.Core.Jvm" Version="1.10.2.1" /> <PackageReference Include="Xamarin.KotlinX.Coroutines.Android" Version="1.10.2.1" /> <!-- AndroidX base --> <PackageReference Include="Xamarin.AndroidX.Core" Version="1.17.0" /> <PackageReference Include="Xamarin.AndroidX.Core.Core.Ktx" Version="1.17.0" /> <PackageReference Include="Xamarin.AndroidX.Annotation" Version="1.9.1.5" /> <PackageReference Include="Xamarin.AndroidX.Annotation.Experimental" Version="1.5.1.1" /> <PackageReference Include="Xamarin.AndroidX.Startup.StartupRuntime" Version="1.2.0.5" /> <PackageReference Include="Xamarin.AndroidX.Work.Work.Runtime.Ktx" Version="2.10.5" /> <PackageReference Include="Xamarin.AndroidX.Room.Room.Ktx" Version="2.8.1" /> <PackageReference Include="Xamarin.AndroidX.Sqlite.Framework" Version="2.6.1" /> <PackageReference Include="Xamarin.AndroidX.DataStore.Preferences" Version="1.1.7.1" /> <!-- Lifecycle family (чтобы не было конфликтов) --> <PackageReference Include="Xamarin.AndroidX.Lifecycle.Common" Version="2.9.4" /> <PackageReference Include="Xamarin.AndroidX.Lifecycle.Runtime" Version="2.9.4" /> <PackageReference Include="Xamarin.AndroidX.Lifecycle.Runtime.Android" Version="2.9.4" /> <PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.9.4" /> <PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData.Core" Version="2.9.4" /> <!-- Material & UI --> <PackageReference Include="Xamarin.Google.Android.Material" Version="1.13.0" /> <!-- Firebase core set --> <PackageReference Include="Xamarin.Firebase.Common" Version="122.0.1" /> <PackageReference Include="Xamarin.Firebase.Common.Ktx" Version="121.0.0.7" /> <PackageReference Include="Xamarin.Firebase.Components" Version="119.0.0.1" /> <PackageReference Include="Xamarin.Firebase.Datatransport" Version="120.0.1" /> <PackageReference Include="Xamarin.Firebase.Encoders" Version="117.0.0.23" /> <PackageReference Include="Xamarin.Firebase.Encoders.JSON" Version="118.0.1.15" /> <PackageReference Include="Xamarin.Firebase.Encoders.Proto" Version="116.0.0.18" /> <PackageReference Include="Xamarin.Firebase.Installations" Version="119.0.1" /> <PackageReference Include="Xamarin.Firebase.Installations.InterOp" Version="117.2.0.11" /> <PackageReference Include="Xamarin.Firebase.Measurement.Connector" Version="120.0.1.11" /> <!-- Google Play Services --> <PackageReference Include="Xamarin.GooglePlayServices.Base" Version="118.9.0" /> <PackageReference Include="Xamarin.GooglePlayServices.Basement" Version="118.9.0" /> <PackageReference Include="Xamarin.GooglePlayServices.Tasks" Version="118.4.0" /> <PackageReference Include="Xamarin.GooglePlayServices.CloudMessaging" Version="117.3.0.7" /> <PackageReference Include="Xamarin.GooglePlayServices.Stats" Version="117.1.0.7" /> <PackageReference Include="Xamarin.Google.Android.DataTransport.TransportApi" Version="4.0.0.5" /> <!-- Guava / DataTransport --> <PackageReference Include="Xamarin.Google.Guava.ListenableFuture" Version="1.0.0.29" /> <PackageReference Include="Xamarin.Google.Android.DataTransport.TransportBackendCct" Version="4.0.0.5" /> <PackageReference Include="Xamarin.Google.Android.DataTransport.TransportRuntime" Version="4.0.0.5" /> <PackageReference Include="Xamarin.JavaX.Inject" Version="1.0.0.21" /> </ItemGroup>
В этом примере важны атрибуты Bind.
<AndroidMavenLibrary Include="ru.rustore.sdk:push-core" Version="7.0.0" Repository="https://artifactory-external.vkpartner.ru/artifactory/maven" Bind="false" />
По умолчанию Bind="true", а это значит что будет сгенерирована обёртка на C#. Для зависимостей это нужно только когда типы из этих библиотек используются в качестве входных-выходных параметров public функций основной сборке, а если обёртка не нужна то и править возможные ошибки тоже не нужно.
После сборки, Visual Studio Code насыпал кучу ошибок. Ошибки кликабельные. Если кликнуть на первую ошибку увидим что-то такое:

Из скриншота видно что какая-то обертка всё таки получилась, но c имплементацией интерфейса что-то пошло не так, причём в двух местах: NotificationParams.CREATOR и RemoteMessage.CREATOR. Так как эти классы нам пока не нужны давайте укажем сборщику не делать обёртку.
Для этого открываем файл Transorms/Metadata.xml.Атрибут path копируем в комментарии над объявлением класса. Получилось вот так:
<metadata> <remove-node path="/api/package[@name='com.vk.push.common.messaging']/class[@name='NotificationParams.CREATOR']"/> <remove-node path="/api/package[@name='com.vk.push.common.messaging']/class[@name='RemoteMessage.CREATOR']"/> </metadata>
После сборки можно заметить что ошибки с этими классами исчезли. Жмём на следующую ошибку: 'ILoggerInvoker' does not implement interface member 'ILogger.CreateLogger(string)'. 'ILoggerInvoker.CreateLogger(string)' cannot implement 'ILogger.CreateLogger(string)' because it does not have the matching return type of 'ILogger'.
Ошибка такая же, но в этом случае логгер нам может пригодится, мало ли чего?
Самое интересное что если пролистать вниз этот метод всё таки будет реализован:
// Metadata.xml XPath method reference: path="/api/package[@name='ru.rustore.sdk.pushclient.common.logger']/class[@name='DefaultLogger']/method[@name='createLogger' and count(parameter)=1 and parameter[1][@type='java.lang.Object']]" [Register ("createLogger", "(Ljava/lang/Object;)Lcom/vk/push/common/Logger;", "")] public unsafe global::Com.VK.Push.Common.ILogger CreateLogger (global::Java.Lang.Object p0) { const string __id = "createLogger.(Ljava/lang/Object;)Lcom/vk/push/common/Logger;"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue ((p0 == null) ? IntPtr.Zero : ((global::Java.Lang.Object) p0).Handle); var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, __args); return global::Java.Lang.Object.GetObject<global::Com.VK.Push.Common.ILogger> (__rm.Handle, JniHandleOwnership.TransferLocalRef)!; } finally { global::System.GC.KeepAlive (p0); } }
Странно, ну ладно... Давайте посмотрим что за зверь такой ru.rustore.sdk.pushclient.common.logger.
Для этого переходим во внутрь aar (я это делаю с помощью mc):
mc /obj/Debug/net9.0-android/library_project_jars/pushclient-7.0.0.aar
Там переходим в консольный режим (ctrl+o), запускаем jadx-gui classes.jar

Видно что это действительно интерфейс и в нём есть метод createLogger. Просят реализовать - давайте реализуем. Для этого в корне проекта создадим класс ILoggerInvoker:
namespace RU.Rustore.Sdk.Pushclient.Common.Logger; internal partial class ILoggerInvoker { Com.VK.Push.Common.ILogger Com.VK.Push.Common.ILogger.CreateLogger(string tag) { return CreateLogger(tag); } }
Для DefaultLogger решение аналогичное:
namespace RU.Rustore.Sdk.Pushclient.Common.Logger; public partial class DefaultLogger { Com.VK.Push.Common.ILogger Com.VK.Push.Common.ILogger.CreateLogger(string tag) { return CreateLogger(tag); } }
Собираем и ... ошибок сборки нет. Теперь переходим к тестовому проекту.
Добавляем в манифест приложения для андроид:
<meta-data android:name="ru.rustore.sdk.pushclient.default_notification_channel_id" android:value="@string/notifications_data_push_channel_id" />
Добавляем пермишен:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
strings.xml в ресурсы андроида
<resources> <string name="app_name">RuStore Push Java Example</string> <!-- Notification --> <string name="notifications_data_push_channel_id">data_push_channel</string> <string name="notifications_data_push_channel_name">Data push channel</string> <string name="notifications_notification_push_channel_id">notification_push_channel</string> <string name="notifications_notification_push_channel_name">Notification push channel</string> </resources>
NotificationManagerWrapper для создания и показа пуша
using Android.Content; using Android.Content.PM; using AndroidX.Core.App; using AndroidX.Core.Content; namespace Sample.Wrapper; public record AppNotification(int Id, string Title, string Message, string ChannelId, string ChannelName); public class NotificationManagerWrapper { private readonly NotificationManagerCompat _notificationManager; private static NotificationManagerWrapper _instance; private NotificationManagerWrapper(NotificationManagerCompat notificationManager) { _notificationManager = notificationManager; } public static NotificationManagerWrapper GetInstance(Context context) { if (_instance == null) { _instance = new NotificationManagerWrapper(NotificationManagerCompat.From(context)); } return _instance; } public void CreateNotificationChannel(string channelId, string channelName) { var builder = new NotificationChannelCompat.Builder(channelId, NotificationManagerCompat.ImportanceDefault) .SetName(channelName); _notificationManager.CreateNotificationChannel(builder.Build()); } public void ShowNotification(Context context, AppNotification data) { // Создаём билдер уведомления var builder = new NotificationCompat.Builder(context, data.ChannelId) .SetContentTitle(data.Title) .SetContentText(data.Message) .SetSmallIcon(Resource.Drawable.dotnet_bot); // замените на свой значок // Проверяем наличие канала if (_notificationManager.GetNotificationChannel(data.ChannelId) == null) { CreateNotificationChannel(data.ChannelId, data.ChannelName); } // Проверка разрешения на уведомления (Android 13+) if (ContextCompat.CheckSelfPermission(context, Android.Manifest.Permission.PostNotifications) != Permission.Granted) { return; } _notificationManager.Notify(data.Id, builder.Build()); } }
Фоновый андроид-сервис для приёма пушей:
using Android.App; using Android.Util; using Microsoft.Maui.Controls.Internals; using RU.Rustore.Sdk.Pushclient.Messaging.Exception; using RU.Rustore.Sdk.Pushclient.Messaging.Model; using RU.Rustore.Sdk.Pushclient.Messaging.Service; using Sample.Wrapper; namespace Sample; [Preserve(AllMembers = true)] [Service(Exported = true)] [IntentFilter(new[] { "ru.rustore.sdk.pushclient.MESSAGING_EVENT" })] public class PushListenerService : RuStoreMessagingService { private const string LogTag = "PushListenerService"; private NotificationManagerWrapper? _notificationManagerWrapper; public override void OnCreate() { base.OnCreate(); _notificationManagerWrapper = NotificationManagerWrapper.GetInstance(this); } public override void OnNewToken(string token) { Log.Debug(LogTag, $"OnNewToken token = {token}"); } public override void OnMessageReceived(RemoteMessage message) { base.OnMessageReceived(message); var channelInfo = GetChannelInfo(); var notification = new AppNotification( message.GetHashCode(), message.Notification?.Title, message.Notification?.Body, channelInfo.Item1, channelInfo.Item2 ); _notificationManagerWrapper.ShowNotification(this, notification); } private (string, string) GetChannelInfo() { string channelId = GetString(Resource.String.notifications_data_push_channel_id); string channelName = GetString(Resource.String.notifications_data_push_channel_name); return (channelId, channelName); } public override unsafe void OnError(IList<RuStorePushClientException> errors) { base.OnError(errors); } }
Всё это очень напоминает подключение пушей Firebase.
Не забываем включить поддержку unsafe, но если что компилятор напомнит.
Далее переходим в раздел Проверка возможности получать push-уведомления официальной документации. Проверяем ничего ли мы не забыли. Самое главное нужно добавить fingerprint(отпечаток) подписи (без этого не получить project id) и настроить подпись apk при сборке. Без этого пуши не заработают и будут сыпаться эксепшены.
В MainApplication добавляем инициализацию, всё как в документации:
public override void OnCreate() { base.OnCreate(); RuStorePushClient.Instance.Init(this, "push project id", new MainActivity.MyLogger()); }
А в MainActivity запрашиваем перемешен
protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); const int requestNotification = 0; string[] notiPermission = { Manifest.Permission.PostNotifications }; if (CheckSelfPermission(Manifest.Permission.PostNotifications) != Permission.Granted) { RequestPermissions(notiPermission, requestNotification); } }
Запускаем, открываем Logcat ищем push-токен по тэгу PushListenerService, копируем токен в интерфейс отправки тестовых сообщений в Rustore. Жмём отправить.

Победа! Пуши приходят, но почему-то задваиваются при показе в шторке. Думал что это где-то у меня ошибка. Запустил официальный пример на Котлин - там такое же поведение, просто второй пуш не показывается, т.к. в сервисе эксепшен из-за того что иконка мне указана.
Выводы:
Биндинг либы из Rustore SDK делать несложно, хотя количество ошибок сборки может испугать неподготовленного человека.
Документация для java/kotlin подробная, библиотека хорошо обложена логированием в логкат. Во время отладки сыпались эксепшены, из которых понятно как их вылечить, для каждого было описание в официальной документации.
При желании к мобильному приложению на maui можно прикрутить любые нативные библиотеки.
При создании биндингов для классов на Котлин иногда встречаются нюансы, но это уже тема отдельной статьи. С пушами получилось всё стандартно.
С релизом .Net 9 биндинги для нативных библиотек для андроида стало делать сильно проще. Даже для таких больших как rustore push или rustore pay
Ссылка на репозиторий: тынц
Рядом с итоговым репозиторием лежат репозитории разной степени готовности. Например, rustore pay уже видит добавленные в интерфейсе Rustore покупки, Remote Config - видит конфиги на сервере, а Ok.Tracer - шлёт крэши и показывает их в Web интерфейсе.Буду рад вашим пул реквестам, вопросам и комментариям.
