Пишем приложения для Sony SmartWatch и SmartWatch 2

Sony SmartWatch – достаточно интересный девайс своего времени, разработку под который почему-то обошли стороной на хабре. Ну раз так – давайте исправлять! В качестве примера мы разработаем простое приложение для управлением любым аудио-плеером.

Статья предназначена для тех, кто уже хотя бы минимально знает, с какой стороны держать инструменты для разработки под Android, а так же тех, кто видел те самые часы или читал про них обзоры, и, соответственно, представляет их функционал. Разрабатывать будем сразу под первую и вторую версии SmartWatch.

Установка необходимых библиотек

Запускаем Android SDK Manager и идём в меню Tools -> Manage Add-on Sites

На вкладке User Defined Sites добавляем адрес с SDK под часы:
http://dl-developer.sonymobile.com/sdk_manager/Sony-Add-on-SDK.xml

На самом деле, данный SDK поддерживает не только часы, но и некоторые другие хитрые устройства от Sony, такие как например Smart Headset… Но нам пока интересны только часы.

И теперь выбираем новые, появившиеся в списке пакеты и устанавливаем их:

Кроме собственно необходимых библиотек, после установки обязательно загляните в папку [директория Android SDK]/sdk/add-ons/addon-sony_add-on_sdk_2_1-sony-16/samples. Там есть примеры использованию абсолютно всех возможностей часиков, мы поговорим только об избранных.

Эмулятор часов

В принципе, разрабатывать под реальные часы гораздо проще и удобнее, но тем не менее, вместе с SDK идёт и эмулятор. Для его использования пойдём в AVD Manager и создадим одно из появившихся в списке новых устройство от Sony, например, Xperia T. Главное, что бы в качестве параметра Target был выбран Sony Add-on SDK.

Теперь, если запустить такое устройство на эмуляцию, то в списке приложений на эмулируемом устройстве можно найти Accessory emulator

Который эмулирует необходимые нам часики (и не только, как уже упоминалось выше).

План проекта

Ну а теперь, что именно мы будем разрабатывать? Как мне кажется, делать всякие hello word скучно, так что напишем приложение для управления плеером! Любым плеером на телефоне. Вот это подходящий масштаб действий. 😉

  • Приложение будет управляться жестами и кликами. Жест справа-налево и обратно – это следующий/предыдущий трек, вверх/вниз – громче/тише. Клик в центре – поставить на паузу/продолжить воспроизведение.
  • Кроме самого экрана приложения реализуем виджет (для часов), который по клику будет вызывать основное окно программы.
  • Сделаем заготовку для экрана настроек приложения – ну просто про запас.
  • Поддерживать оно должно обе версии SmartWatch (первую и вторую, как подсказываем Кэп).

Подключаем библиотеки к проекту в IntelliJ IDEA

Поскольку я использую IntelliJ IDEA, то и пример приводить на ней. Для начала – создадим проект, в качестве версии SDK выбираем вариант от Sony.

Кроме того, для работы мы подключим к проекту пару модулей из той самой папки samples– в частности SmartExtensions/SmartExtensionAPI и SmartExtensions/SmartExtensionUtils. Вторую, теоретически, можно не подключать, и написать всё её содержимое с нуля, но мы, адепты тёмной стороны силы, ценим эффективность и удобство, а желание писать с нуля то, что уже существует нам чуждо. Инструкции по самому подключению я убрал под спойлер, благо там всё просто.

Подключаем библиотеки.

Идём в File -> Project Structure, там – на закладку Modules, кликаем по “плюсику” и выбираем Import Module

Находим папку SmartExtensionAPI:

Дальше ОК и Next->Next->Next до победного конца, как в старые добрые времена.
После чего подключаем к основному проекту добавленный модуль.

Аналогичным образом подключаем и SmartExtensionUtils.

Настраиваем базовые классы и параметры

Начнём с манифеста.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"           package="com.smartwatch_habra_demo">     <uses-sdk android:minSdkVersion="9"               android:targetSdkVersion="16"/>     <uses-permission android:name="com.sonyericsson.extras.liveware.aef.EXTENSION_PERMISSION" />      <application android:label="Демо-приложения для часов для хабра" android:icon="@drawable/icon">          <activity                 android:name="DemoConfigActivity"                 android:label="Экран с настройками" >             <intent-filter>                 <action android:name="android.intent.action.MAIN" />             </intent-filter>         </activity>          <service android:name="DemoReceiverService" />          <receiver                 android:name="DemoExtensionReceiver"                 android:permission="com.sonyericsson.extras.liveware.aef.HOSTAPP_PERMISSION" >             <intent-filter>                  <!-- Generic extension intents. -->                 <action android:name="com.sonyericsson.extras.liveware.aef.registration.EXTENSION_REGISTER_REQUEST" />                 <action android:name="com.sonyericsson.extras.liveware.aef.registration.ACCESSORY_CONNECTION" />                 <action android:name="android.intent.action.LOCALE_CHANGED" />                  <!-- Notification intents -->                 <action android:name="com.sonyericsson.extras.liveware.aef.notification.VIEW_EVENT_DETAIL" />                 <action android:name="com.sonyericsson.extras.liveware.aef.notification.REFRESH_REQUEST" />                  <!-- Widget intents -->                 <action android:name="com.sonyericsson.extras.aef.widget.START_REFRESH_IMAGE_REQUEST" />                 <action android:name="com.sonyericsson.extras.aef.widget.STOP_REFRESH_IMAGE_REQUEST" />                 <action android:name="com.sonyericsson.extras.aef.widget.ONTOUCH" />                 <action android:name="com.sonyericsson.extras.liveware.extension.util.widget.scheduled.refresh" />                  <!-- Control intents -->                 <action android:name="com.sonyericsson.extras.aef.control.START" />                 <action android:name="com.sonyericsson.extras.aef.control.STOP" />                 <action android:name="com.sonyericsson.extras.aef.control.PAUSE" />                 <action android:name="com.sonyericsson.extras.aef.control.RESUME" />                 <action android:name="com.sonyericsson.extras.aef.control.ERROR" />                 <action android:name="com.sonyericsson.extras.aef.control.KEY_EVENT" />                 <action android:name="com.sonyericsson.extras.aef.control.TOUCH_EVENT" />                 <action android:name="com.sonyericsson.extras.aef.control.SWIPE_EVENT" />                 <action android:name="com.sonyericsson.extras.aef.control.OBJECT_CLICK_EVENT" />                 <action android:name="com.sonyericsson.extras.aef.control.MENU_ITEM_SELECTED" />             </intent-filter>         </receiver>     </application> </manifest> 

Суть происходящего такова: мы создаём в приложении класс, который будет принимать события от часов, передавать их в сервис обработки, который и будет производить некие осмысленные действия. Единственная activity нам нужна для окна настроек, если же таковое нам не нужно – можно было бы выкинуть её совсем.

Класс-receiver совсем простой:

DemoExtensionReceiver.java

public class DemoExtensionReceiver extends BroadcastReceiver {      @Override     public void onReceive(final Context context, final Intent intent) {         intent.setClass(context, DemoReceiverService.class);         context.startService(intent);     } } 

Ну а теперь перейдём к самому сервису:

DemoReceiverService.java

public class DemoReceiverService extends ExtensionService {      public static final String EXTENSION_KEY = "com.smartwatch_habra_demo"; //todo не смог найти в документации подробностей о применимости, так что просто копипастим из примеров по шаблону "пакет.приложения"      public DemoReceiverService() {         super(EXTENSION_KEY);     }      @Override     protected RegistrationInformation getRegistrationInformation() {         return new DemoRegistrationInformation(this);     }      @Override     protected boolean keepRunningWhenConnected() {//нам не нужно постоянно держать сервис работающим         return false;     }      @Override     public WidgetExtension createWidgetExtension(String hostAppPackageName) { //возвращаем объект виджета         return new DemoWidget(this,hostAppPackageName);     }      @Override     public ControlExtension createControlExtension(String hostAppPackageName) {//возвращаем объект основной программы         boolean IsSmartWatch2= DeviceInfoHelper.isSmartWatch2ApiAndScreenDetected(                 this, hostAppPackageName);         if (IsSmartWatch2){             return new DemoControl2(this,hostAppPackageName);         }else{             return new DemoControl(this,hostAppPackageName);         }     } } 

Достаточно лаконично, правда? Ключевые моменты поясняются комментариями, вопросов вроде не должно возникнуть. ControlExtension нам нужен для обработки и рисования основного приложения на часах, WidgetExtension – для тех же целей, но уже для виджета.

А вот RegistrationInformation – это информация для регистрации нашего расширения в программе управления часами так сказать.

DemoRegistrationInformation.java

public class DemoRegistrationInformation extends RegistrationInformation {     public static final int WIDGET_WIDTH_SMARTWATCH = 128;     public static final int WIDGET_HEIGHT_SMARTWATCH = 110;      public static final int CONTROL_WIDTH_SMARTWATCH = 128;     public static final int CONTROL_HEIGHT_SMARTWATCH = 128;     public static final int CONTROL_WIDTH_SMARTWATCH_2 = 220;     public static final int CONTROL_HEIGHT_SMARTWATCH_2 = 176;      Context mContext;      protected DemoRegistrationInformation(Context context) {         if (context == null) {             throw new IllegalArgumentException("context == null");         }         mContext = context;     }      @Override     public ContentValues getExtensionRegistrationConfiguration() {         String iconHostapp = ExtensionUtils.getUriString(mContext, R.drawable.icon);          ContentValues values = new ContentValues();         values.put(Registration.ExtensionColumns.CONFIGURATION_ACTIVITY,DemoConfigActivity.class.getName()); //активити, которое будет отображаться в меню "настройки расширения". Если оно нам не нужно - убираем параметр совсем.         values.put(Registration.ExtensionColumns.CONFIGURATION_TEXT,"Настройки демо-расширения");//а это текст, отображащийся в качестве пункта меню программы управления часами. Если оно нам не нужно - убираем параметр совсем.          values.put(Registration.ExtensionColumns.NAME, "Хабра-демо-расширение");//имя, отображаемое в списке приложений внутри программы управления часами         values.put(Registration.ExtensionColumns.EXTENSION_KEY,DemoReceiverService.EXTENSION_KEY); //уникальный ключ расширения          values.put(Registration.ExtensionColumns.HOST_APP_ICON_URI, iconHostapp); //иконка в списке приложений в телефоне         values.put(Registration.ExtensionColumns.EXTENSION_ICON_URI, iconHostapp); //иконка в списке приложений на самих часах, в идеале 48x48         values.put(Registration.ExtensionColumns.NOTIFICATION_API_VERSION,getRequiredNotificationApiVersion());//нужная версия механизма уведомлений         values.put(Registration.ExtensionColumns.PACKAGE_NAME, mContext.getPackageName());          return values;     }      @Override     public int getRequiredNotificationApiVersion() { //нам не нужно управление нотификациями         return 0;     }      @Override     public int getRequiredSensorApiVersion() { //нам не нужна инфа с сенсоров вроде акселерометра         return 0;     }      //---------------------------------------------     //всё что нужно для поддержки виджета     //---------------------------------------------      @Override     public boolean isWidgetSizeSupported(final int width, final int height) {         return (width == WIDGET_WIDTH_SMARTWATCH && height == WIDGET_HEIGHT_SMARTWATCH);     }      @Override     public int getRequiredWidgetApiVersion() { //для поддержки первых часов         return 1;     }      //---------------------------------------------     //всё что нужно для поддержки контроллера     //---------------------------------------------      @Override     public int getRequiredControlApiVersion() { //для поддержки первых часов         return 1;     }      @Override     public int getTargetControlApiVersion() { //для поддержки второй версии часов         return 2;     }      @Override     public boolean isDisplaySizeSupported(int width, int height) {         return (width == CONTROL_WIDTH_SMARTWATCH_2 && height == CONTROL_HEIGHT_SMARTWATCH_2)                 || (width == CONTROL_WIDTH_SMARTWATCH && height == CONTROL_HEIGHT_SMARTWATCH);     } } 

Здесь стоит остановиться поподробнее. Дело в том, что скачанное нами API от Sony – универсальное для целой пачки устройств от Sony, и никто не мешает нам написать приложение (расширение), которое может запуститься на всех этих устройствах разом. Или только на избранных из них.

Раз такое дело, нам надо сообщить, какие размеры экранов и версии API для сенсоров, виджетов и т.п. нам нужно поддержать. Нам нужно указать:

  • Поддержка разных сенсоров (акселерометров и т.п.) – getRequiredSensorApiVersion. Нам оно не надо совсем, так что версия API = 0.
  • Нотификации (Notification) — всплывающие сообщения-уведомления; нам они тоже не нужны. Так что в getRequiredNotificationApiVersion снова 0.
  • “Контроллер” – это то самое “обычное окно программы на часах”. Для него нам нужно определить версию. Кроме того, нам придётся указать поддерживаемые размеры экранов первых и вторых часов, и только их, никакие иные устройства нам не нужны. Поэтому передаём:
    • getRequiredControlApiVersion – версию 1 (для поддержки первой версии часов). Если бы передали 2 – поддерживались бы только Smartwatch 2, на первых бы не запустилось.
    • getTargetControlApiVersion – целевая версия API, здесь 2 для опять же поддержки Smartwatch 2
    • isDisplaySizeSupported – получаем размеры экрана устройства и определяем, хотим ли мы запускаться на нём или нет.

  • “Виджет” (Widget) – это изображение в списке виджетов. Аналогично, нужно указать требуемую версию и размеры экрана. Важный момент: вторая версия часов виджеты не поддерживает. Увы.

Плюс пачка параметров в getExtensionRegistrationConfiguration, но там всё понятно из комментариев.

Основное окно программы

Здесь важно осознать следующим момент. На часы в первой версии часов мы можем отправлять только изображения. Картинки. Всё. Ничего больше. Иным способом рисовать мы не можем. Во второй версии появились расширенные контроллеры, но мы-то изначально пишем для поддержки обоих версий, так что только изображения.

Если же вы хотите использовать для рендера возможности Layout, например, отрендерить компоненты – без проблем, но координаты кликов и прочее взаимодействие придётся обрабатывать вручную. Безрадостная перспектива… Но тем не менее. Вот так будет выглядеть наша картинка:

А вот так — код, который будет за всё ответит:

DemoControl.cs

public class DemoControl extends ControlExtension {      static final Rect buttonStopPlaySmartWatch = new Rect(43, 42, 85, 88);      public DemoControl(Context context, String hostAppPackageName) {         super(context, hostAppPackageName);     }      @Override     public void onTouch(final ControlTouchEvent event) {//реакция на клики         if (event.getAction() == Control.Intents.CLICK_TYPE_SHORT) {             if (buttonStopPlaySmartWatch.contains(event.getX(), event.getY())){                 MusicBackgroundControlWrapper.TogglePausePlay(mContext);             }         }     }      @Override     public void onSwipe(int direction) {//реакция на жесты         if (direction== Control.Intents.SWIPE_DIRECTION_UP){             MusicBackgroundControlWrapper.VolumeUp(mContext);         }         if (direction==Control.Intents.SWIPE_DIRECTION_DOWN){             MusicBackgroundControlWrapper.VolumeDown(mContext);         }         if (direction==Control.Intents.SWIPE_DIRECTION_LEFT){             MusicBackgroundControlWrapper.Next(mContext);         }         if (direction==Control.Intents.SWIPE_DIRECTION_RIGHT){             MusicBackgroundControlWrapper.Prev(mContext);         }     }      @Override     public void onResume() {//рисуем изображение         Bitmap mPicture = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.control_picture);         showBitmap(mPicture);     } } 

Назначение событий onSwipe и onTouch говорят сами за себя, onResume вызывается каждый раз, как оно программы будет видно, например, часы вышли из спячки или была выбрана иконка приложения. В принципе, этого достаточно для большинства взаимодействий с приложением.

MusicBackgroundControlWrapper – это небольшой самописный класс, предназначенный для управления плеером с использованием эмуляции нажатий мультимедийных клавиш. Нормально работает не со всеми плеерами и телефонами, но там где работает – работает на ура. Если знаете лучший способ (с поддержкой Android 2.3 и выше!) – поделитесь пожалуйста в комментариях.

MusicBackgroundControlWrapper.java

public class MusicBackgroundControlWrapper {     public static void KeyPressDownAndUp(int key,Context context){         long eventtime = SystemClock.uptimeMillis() - 1;          Intent downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);         KeyEvent downEvent = new KeyEvent(eventtime, eventtime,                 KeyEvent.ACTION_DOWN, key, 0);         downIntent.putExtra(Intent.EXTRA_KEY_EVENT, downEvent);         context.sendOrderedBroadcast(downIntent, null);          eventtime++;         Intent upIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);         KeyEvent upEvent = new KeyEvent(eventtime, eventtime,                 KeyEvent.ACTION_UP, key, 0);         upIntent.putExtra(Intent.EXTRA_KEY_EVENT, upEvent);         context.sendOrderedBroadcast(upIntent, null);     }      public static void VolumeUp(Context context){         AudioManager audioManager =(AudioManager)context.getSystemService(Context.AUDIO_SERVICE);          int max=audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);         int current=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);          if (current<max){             current++;         }          audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,                 current,0);     }      public static void VolumeDown(Context context){         AudioManager audioManager =(AudioManager)context.getSystemService(Context.AUDIO_SERVICE);          int current=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);          if (current>0){             current--;         }          audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,                 current,0);     }      public static void TogglePausePlay(Context context){         KeyPressDownAndUp(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,context);     }      public static void Next(Context context){         KeyPressDownAndUp(KeyEvent.KEYCODE_MEDIA_NEXT, context);     }      public static void Prev(Context context){         KeyPressDownAndUp(KeyEvent.KEYCODE_MEDIA_PREVIOUS, context);     } } 

Для поддержки второй версии часов мы унаследуем DemoControl2 от DemoControl, с парой изменений – в onResume() будем передавать другое изображение, а в onTouch – проверять иные координаты.

DemoControl2.java

public class DemoControl2 extends DemoControl {     static final Rect buttonStopPlaySmartWatch2 = new Rect(59, 52, 167, 122);      public DemoControl2(Context context, String hostAppPackageName) {         super(context, hostAppPackageName);     }      @Override     public void onTouch(final ControlTouchEvent event) {//реакция на клики         if (event.getAction() == Control.Intents.CLICK_TYPE_SHORT) {             if (buttonStopPlaySmartWatch2.contains(event.getX(), event.getY())){                 MusicBackgroundControlWrapper.TogglePausePlay(mContext);             }         }     }      @Override     public void onResume() {//рисуем изображение         Bitmap mPicture = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.control_picture2);         showBitmap(mPicture);     } } 

Виджет

Итак, виджет. Каноничный виджет имеет разрешение 92×92 пикселя для первой версии часов и не поддерживаются в принципе для второй. Можно растянуть его и на бОльшие разрешения (вплоть до 128×110), но он тогда будет выбиваться из стилистики и закрывать стандартные элементы управления и индикации.

Нам от него понадобится только одно действие – по клику запускать наше основное приложение на часах. Класс, отвечающий за это тоже очень простой, все подробности в комментариях.

DemoWidget.java

public class DemoWidget extends WidgetExtension {      public DemoWidget(Context context, String hostAppPackageName) {         super(context, hostAppPackageName);     }      @Override     public void onStartRefresh() { //Когда виджет становится видимым и/или обновляется.         showBitmap(new DemoWidgetImage(mContext).getBitmap());     }      @Override     public void onStopRefresh() { //Когда виджет перестаёт быть видимым. Нам ничего не нужно делать, мы и так не обновляем его и не анимируем.     }      @Override     public void onTouch(final int type, final int x, final int y) {         if (!SmartWatchConst.ACTIVE_WIDGET_TOUCH_AREA.contains(x, y)) { //если кликнули вне иконки приложения - ничего не делаем             return;         }          //по клику (быстрому или долгому) запускаем основное окно программы         if (type == Widget.Intents.EVENT_TYPE_SHORT_TAP || type==Widget.Intents.EVENT_TYPE_LONG_TAP) {             Intent intent = new Intent(Control.Intents.CONTROL_START_REQUEST_INTENT);             intent.putExtra(Control.Intents.EXTRA_AEA_PACKAGE_NAME, mContext.getPackageName());             intent.setPackage(mHostAppPackageName);             mContext.sendBroadcast(intent, Registration.HOSTAPP_PERMISSION);         }     } } 

Хотя есть там и интересный момент. В комплекте с API, среди утилит есть класс специально для виджетов, самостоятельно рендерящий Layout в картинку. Грех такой возможностью не воспользоваться, хотя бы и в целях обучения. Рендерить будем через класс DemoWidgetImage.

DemoWidgetImage.java

public class DemoWidgetImage extends SmartWatchWidgetImage {      public DemoWidgetImage(Context context) {         super(context);         setInnerLayoutResourceId(R.layout.music_widget_image);     }      @Override     protected void applyInnerLayout(LinearLayout innerLayout) {         //даже если ничего не делаем с содержимым - переопределить обязаны. Угу.     } } 

Окно настроек

Ну тут нужно совсем минимум. Поскольку в классе DemoRegistrationInformation мы уже прописали имя активити, то тут нам сейчас остаётся только заполнить её ну хоть чем-то. Даже комментировать не буду. Просто код.

DemoConfigActivity.java

public class DemoConfigActivity extends Activity {     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.config);     } } 

config.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"               android:orientation="vertical"               android:layout_width="match_parent"               android:layout_height="match_parent">      <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:textAppearance="?android:attr/textAppearanceMedium"             android:text="Демонстрационный текст"             android:id="@+id/textView" android:layout_gravity="center_horizontal"/> </LinearLayout> 

Как опубликовать приложение в Google Play

Что бы ваше приложение находилось утилитой управления часами в магазине приложений – нужно добавить в текст описания программы на Google Play:

  • Для поддержки SmartWatch — “LiveWare extension for SmartWatch”
  • Для поддержки SmartWatch 2 – “Smart Connect extension for SmartWatch 2”
  • Если нужны оба – добавляем соответственно обе строки.

Что характерно, установить приложение сможет и человек, у которого самих часов нет. Установить, не запустить и влепить минимальную оценку, да. Привыкайте, это мир Google Play! Но нам ведь не важна оценка, нам важно, что мир становится чуточку лучше, верно…?

Что ещё можно доделать в приложении-примере

  • Окно настроек (сделать, например инвертирование жестов).
  • Более корректный и универсальный способ управления плеерами. В Android 4.4 уже реализован нужный API (Remote controllers кажется называется), а вот для более старых – проблема.
  • Сделать (придумать, найти) автоматический расчет координат для объектов, находящихся на вьюшке. Что бы руками не считать каждый раз, вдруг Sony создаст третьи часы с третьим разрешением.

Результат нашей работы

Исходный код примера из статьи

github.com/Newbilius/smartwatch_habra_demo

Источник в лице сайта Sony

developer.sonymobile.com/knowledge-base/sony-add-on-sdk/

И повторюсь, если возникли вопросы по другим фичам часов – смотрите папку examples (полный путь был приведён выше), там есть примеры использования абсолютно всех датчиков и возможностей. Цель этой статьи – дать вам возможность совершить “быстрый старт” и заинтересовать, надеюсь, у меня это получилось сделать.

P.S. Если вам нужно готовое приложение, описанное в этой статье, но нет желание заниматься разработкой – в Google Play уже есть такое. В нём уже есть инвертирование горизонтальных жестов, возможность скрыть кнопку “пауза”, плюс режим с “кнопками” вместо жестов. Надеюсь, это не будет посчитано рекламой, ибо ни ссылок, ни названия тут не будет, да и тематика статьи явно иная. Если администрация посчитает этот абзац рекламой – я его удалю.

ссылка на оригинал статьи http://habrahabr.ru/post/210898/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *