Как управлять startActivityForResult на Android

В своем действии я вызываю второе действие из основного действия startActivityForResult. Во втором моем упражнении есть несколько методов, которые завершают это действие (возможно, без результата), однако только один из них возвращает результат.

Например, из основного занятия я вызываю второе. В этом упражнении я проверяю некоторые функции телефона, например, есть ли в нем камера. Если этого не произошло, я закрою это действие. Кроме того, во время подготовки MediaRecorder или MediaPlayer, если возникнет проблема, я закрою это действие.

Если на его устройстве есть камера и запись выполняется полностью, то после записи видео, если пользователь нажимает кнопку «Готово», я отправляю результат (адрес записанного видео) обратно в основное действие.

Как мне проверить результат основной деятельности?


person Hesam    schedule 02.05.2012    source источник
comment
возможный дубликат Как вернуть результат (startActivityForResult) из Активность TabHost?   -  person Code-Apprentice    schedule 02.08.2013


Ответы (13)


Из вашего FirstActivity вызовите SecondActivity, используя метод startActivityForResult().

Например:

int LAUNCH_SECOND_ACTIVITY = 1
Intent i = new Intent(this, SecondActivity.class);
startActivityForResult(i, LAUNCH_SECOND_ACTIVITY);

В вашем SecondActivity установите данные, которые вы хотите вернуть обратно в FirstActivity. Если вы не хотите возвращаться, не устанавливайте никаких.

Например: В SecondActivity, если вы хотите отправить данные:

Intent returnIntent = new Intent();
returnIntent.putExtra("result",result);
setResult(Activity.RESULT_OK,returnIntent);
finish();

Если вы не хотите возвращать данные:

Intent returnIntent = new Intent();
setResult(Activity.RESULT_CANCELED, returnIntent);
finish();

Теперь в вашем классе FirstActivity напишите следующий код для метода onActivityResult().

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == LAUNCH_SECOND_ACTIVITY) {
        if(resultCode == Activity.RESULT_OK){
            String result=data.getStringExtra("result");
        }
        if (resultCode == Activity.RESULT_CANCELED) {
            // Write your code if there's no result
        }
    }
} //onActivityResult

Чтобы лучше реализовать передачу данных между двумя действиями в Kotlin, перейдите по ссылке 'Лучший способ передачи данных между действиями '.

person Nishant    schedule 02.05.2012
comment
Какова цель помещения намерения, когда RESUT_CANCELLED в setResult (RESULT_CANCELED, returnIntent); - person Ismail Sahin; 31.10.2013
comment
@ismail Предположим, что в SecondActivity произошло какое-то исключение, в этом случае вам также нужно вернуть результат в FirstActivity, поэтому вы можете установить результат как "RESULT_CANCELLED" в блоке catch и вернуться к FirstActivty, а в FirstActivity's' 'onActivityResult() вы можете проверить, получили ли вы результат успеха или неудачи. - person Nishant; 31.10.2013
comment
Так что решать вам, если вам не нужно знать причину отмены, вы можете использовать просто setResult (RESULT_CANCELED); без всякого умысла - person Ismail Sahin; 31.10.2013
comment
могу я сделать это в AsynkTask в качестве SecondActivity?, - person Francisco Corrales Morales; 09.05.2014
comment
@FranciscoCorrales Если вам нужно начать SecondActivity с AsyncTask, да, вы можете это сделать. - person Nishant; 17.05.2014
comment
@Nishant Как реализовать то же самое по фрагментам вместо активности? - person Shafi; 15.09.2014
comment
@Shafi нет концепции обратного вызова onActivityResult () для фрагментов, поскольку фрагменты в основном являются частью одного Activity. Если вы хотите общаться между двумя фрагментами, вы можете увидеть эту ссылку developer.android.com/training/basics/fragments/. - person Nishant; 20.09.2014
comment
finish не вызывается после startActivityForResult? - person Lendl Leyba; 30.09.2014
comment
@Lei Leyba Нет finish () не вызывается после вызова startActivityForResult (). First Actvity перейдет в состояние паузы. - person Nishant; 01.10.2014
comment
Для меня это не работает - это то, что я так сильно ненавижу в Android - эта система настолько ненадежна: - / - person Martin Pfeffer; 10.03.2015
comment
Что произойдет, если у нас будет три или более действий? У меня сейчас с этим проблема. Мне нужно вернуться к первому действию с третьего по второе, но я теряю данные в onActivityResult (...) в первом действии после этого и resultCode = CANCELED ... - person Ruslan Berozov; 01.06.2017
comment
Вы можете использовать setResult (RESULT_OK); например непосредственно перед вызовом finish (); без создания нового намерения в новых версиях Android. - person spydon; 08.08.2017
comment
Идеально подходит для некоторых простых случаев использования. - person Damir Varevac; 08.07.2018
comment
Почему метод setResult не упоминается в руководстве developer.android.com/training/basics/ намерения / результат? Мне любопытно узнать, как вы это узнали. - person Harsha Vardhan; 19.07.2019
comment
может ли третье действие вернуть намерение? - person Lendl Leyba; 22.12.2019
comment
Какой второй параметр startActivityForResult() вы указали как 1? - person Elijah Mock; 07.01.2020

Как проверить результат от основной деятельности?

Вам необходимо переопределить Activity.onActivityResult(), а затем проверьте его параметры:

  • requestCode определяет, какое приложение вернуло эти результаты. Это определяется вами, когда вы звоните startActivityForResult().
  • resultCode сообщает вам, успешно ли было выполнено это приложение, не удалось или что-то другое.
  • data содержит любую информацию, возвращаемую этим приложением. Это может быть null.
person Sam    schedule 02.05.2012
comment
Это означает, что requestCode используется только в первом действии и никогда не используется для второго действия? Если у 2-го действия разные подходы, он изменится, но на основе дополнительных намерений, а не на requestCode, верно? Изменить: Да, stackoverflow.com/questions/5104269/ - person JCarlosR; 12.08.2017

Пример

Чтобы увидеть весь процесс в контексте, вот дополнительный ответ. См. мой более полный ответ для получения дополнительных объяснений.

введите описание изображения здесь

MainActivity.java

public class MainActivity extends AppCompatActivity {

    // Add a different request code for every activity you are starting from here
    private static final int SECOND_ACTIVITY_REQUEST_CODE = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // "Go to Second Activity" button click
    public void onButtonClick(View view) {

        // Start the SecondActivity
        Intent intent = new Intent(this, SecondActivity.class);
        startActivityForResult(intent, SECOND_ACTIVITY_REQUEST_CODE);
    }

    // This method is called when the second activity finishes
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // check that it is the SecondActivity with an OK result
        if (requestCode == SECOND_ACTIVITY_REQUEST_CODE) {
            if (resultCode == RESULT_OK) { // Activity.RESULT_OK

                // get String data from Intent
                String returnString = data.getStringExtra("keyName");

                // set text view with string
                TextView textView = (TextView) findViewById(R.id.textView);
                textView.setText(returnString);
            }
        }
    }
}

SecondActivity.java

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    }

    // "Send text back" button click
    public void onButtonClick(View view) {

        // get the text from the EditText
        EditText editText = (EditText) findViewById(R.id.editText);
        String stringToPassBack = editText.getText().toString();

        // put the String to pass back into an Intent and close this activity
        Intent intent = new Intent();
        intent.putExtra("keyName", stringToPassBack);
        setResult(RESULT_OK, intent);
        finish();
    }
}
person Suragch    schedule 05.12.2016
comment
Можно ли это сделать двумя разными приложениями A и b? stackoverflow.com/questions/52975645/ - person Jerry Abraham; 25.10.2018

Дополняя ответ от Nishant, лучший способ вернуть Результат деятельности:

Intent returnIntent = getIntent();
returnIntent.putExtra("result",result);
setResult(RESULT_OK,returnIntent);
finish();

У меня была проблема с

new Intent();

Затем я узнал, что правильный способ использования

getIntent();

чтобы получить текущее намерение.

person Julian Alberto    schedule 26.05.2015
comment
Кажется немного странным создавать новый Intent, который существует только для хранения Bundle и не имеет обычных значений, таких как действие или компонент. Но также кажется немного странным (и потенциально опасным?) Изменять Intent, который использовался для запуска текущего действия. Итак, я поискал источник самого Android и обнаружил, что они всегда создают новый Intent для использования в качестве результата. Например, github.com/aosp-mirror/platform_frameworks_base/blob/ - person spaaarky21; 29.06.2018
comment
Здравствуйте, spaaarky21, спасибо за ваш комментарий. Мне жаль, что я не так ясно объяснил, как я пришел к этому решению. Это было три года назад, и я могу только вспомнить, что мое приложение вылетало из-за нового намерения, это то, что я имел в виду, когда сказал, что у меня проблема. На самом деле я просто пробовал использовать getIntent, потому что в то время это имело смысл, и это сработало! Из-за этого я решил поделиться своим решением. Может быть, это не лучший выбор слов, чтобы сказать наилучшим или правильным образом, но я поддерживаю свое решение. Это то, что решило мою проблему и, видимо, других людей тоже. Спасибо - person Julian Alberto; 29.06.2018
comment
Ух ты! работает отлично. getIntent() кажется идеальным способом вернуть данные в неизвестное действие, откуда оно было вызвано. Спасибо! - person sam; 25.02.2019

Для тех, у кого проблема с неправильным кодом запроса в onActivityResult

Если вы вызываете startActivityForResult() из своего Fragment, requestCode изменяется действием, которому принадлежит фрагмент.

Если вы хотите получить правильный resultCode в своей деятельности, попробуйте следующее:

Изменять:

startActivityForResult(intent, 1); To:

getActivity().startActivityForResult(intent, 1);

person Tomasz Mularczyk    schedule 23.10.2015

ActivityResultRegistry - рекомендуемый подход

ComponentActivity теперь предоставляет ActivityResultRegistry, который позволяет вам обрабатывать потоки _3 _ + _ 4_, а также _5 _ + _ 6_ без переопределения методов в ваших Activity или Fragment, обеспечивает повышенную безопасность типов с помощью ActivityResultContract и предоставляет ловушки для тестирования этих потоков.

Настоятельно рекомендуется использовать API результатов деятельности, представленные в Android 10 Activity 1.2.0-alpha02 и Fragment 1.3.0-alpha02.

Добавьте это в свой build.gradle

def activity_version = "1.2.0-beta01"

// Java language implementation
implementation "androidx.activity:activity:$activity_version"
// Kotlin
implementation "androidx.activity:activity-ktx:$activity_version"

Как использовать готовый контракт

Этот новый API имеет следующие встроенные функции

  1. TakeVideo
  2. PickContact
  3. GetContent
  4. GetContents
  5. OpenDocument
  6. OpenDocuments
  7. OpenDocumentTree
  8. CreateDocument
  9. Набирать номер
  10. Сделать фотографию
  11. Просить разрешения
  12. RequestPermissions

Пример, в котором используется контракт takePicture:

private val takePicture = prepareCall(ActivityResultContracts.TakePicture()) { bitmap: Bitmap? ->
    // Do something with the Bitmap, if present
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    button.setOnClickListener { takePicture() }
}

Так что здесь происходит? Давайте немного разберемся. takePicture - это просто обратный вызов, который возвращает растровое изображение, допускающее значение NULL, - значение NULL зависит от того, был ли процесс onActivityResult успешным. prepareCall затем регистрирует этот вызов в новой функции на ComponentActivity под названием ActivityResultRegistry - мы вернемся к этому позже. ActivityResultContracts.TakePicture() - один из встроенных помощников, которые Google создал для нас, и, наконец, вызов takePicture фактически запускает намерение так же, как вы делали это раньше с Activity.startActivityForResult(intent, REQUEST_CODE).

Как написать индивидуальный договор

Простой контракт, который принимает Int в качестве ввода и возвращает строку, которую запрашиваемое действие возвращает в результате Intent.

class MyContract : ActivityResultContract<Int, String>() {

    companion object {
        const val ACTION = "com.myapp.action.MY_ACTION"
        const val INPUT_INT = "input_int"
        const val OUTPUT_STRING = "output_string"
    }

    override fun createIntent(input: Int): Intent {
        return Intent(ACTION)
            .apply { putExtra(INPUT_INT, input) }
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        return when (resultCode) {
            Activity.RESULT_OK -> intent?.getStringExtra(OUTPUT_STRING)
            else -> null
        }
    }
}

class MyActivity : AppCompatActivity() {

    private val myActionCall = prepareCall(MyContract()) { result ->
        Log.i("MyActivity", "Obtained result: $result")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        button.setOnClickListener {
            myActionCall(500)
        }
    }
}

Дополнительную информацию см. В этой официальной документации.

person Darish    schedule 03.04.2020

Если вы хотите обновить пользовательский интерфейс, указав результат активности, вы не можете использовать this.runOnUiThread(new Runnable() {}. При этом пользовательский интерфейс не будет обновляться с новым значением. Вместо этого вы можете сделать это:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_CANCELED) {
        return;
    }

    global_lat = data.getDoubleExtra("LATITUDE", 0);
    global_lng = data.getDoubleExtra("LONGITUDE", 0);
    new_latlng = true;
}

@Override
protected void onResume() {
    super.onResume();

    if(new_latlng)
    {
        PhysicalTagProperties.this.setLocation(global_lat, global_lng);
        new_latlng=false;
    }
}

Это кажется глупым, но работает очень хорошо.

person DaviF    schedule 08.03.2014

startActivityForResult: устарело в Android X

Для нового способа у нас есть registerForActivityResult.

В Java:

 // You need to create a launcher variable inside onAttach or onCreate or global, i.e, before the activity is displayed
 ActivityResultLauncher<Intent> launchSomeActivity = registerForActivityResult(
     new ActivityResultContracts.StartActivityForResult(),
     new ActivityResultCallback<ActivityResult>() {
              @Override
              public void onActivityResult(ActivityResult result) {
                   if (result.getResultCode() == Activity.RESULT_OK) {
                         Intent data = result.getData();
                         // your operation....
                    }
               }
      });

      public void openYourActivity() {
            Intent intent = new Intent(this, SomeActivity.class);
            launchSomeActivity.launch(intent);
      }

В Котлине:

var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data: Intent? = result.data
        // your operation...
    }
}

fun openYourActivity() {
    val intent = Intent(this, SomeActivity::class.java)
    resultLauncher.launch(intent)
}

Преимущество:

  1. Новый способ - уменьшить сложность, с которой мы столкнулись, когда вызываем действие из фрагмента или из другого действия.
  2. Легко запросите любое разрешение и получите обратный звонок
person Sanjayrajsinh    schedule 24.02.2021
comment
По-прежнему result.resultCode равно 0 при ожидании -1 (Activity.RESULT_OK) в Android API 29. - person Dr.jacky; 22.04.2021

Сначала вы используете startActivityForResult() с параметрами в первом Activity, и если вы хотите отправить данные из второго Activity в первый Activity, тогда передайте значение, используя Intent с методом setResult(), и получите эти данные внутри метода onActivityResult() в первом Activity.

person Dharmendra Pratap    schedule 30.09.2014

Я опубликую новый способ с Android X в кратком ответе (потому что в некоторых случаях вы не нужен таможенный реестр или договор). Если вам нужна дополнительная информация, см .: Получение результата от действия

Важно: на самом деле существует ошибка с обратной совместимостью Android X, поэтому вам нужно добавить fragment_version в свой файл Gradle. В противном случае вы получите исключение. Ошибка API нового результата: для requestCode можно использовать только младшие 16 бит.

dependencies {

    def activity_version = "1.2.0-beta01"
    // Java language implementation
    implementation "androidx.activity:activity:$activity_version"
    // Kotlin
    implementation "androidx.activity:activity-ktx:$activity_version"

    def fragment_version = "1.3.0-beta02"
    // Java language implementation
    implementation "androidx.fragment:fragment:$fragment_version"
    // Kotlin
    implementation "androidx.fragment:fragment-ktx:$fragment_version"
    // Testing Fragments in Isolation
    debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}

Теперь вам просто нужно добавить эту переменную-член вашей активности. При этом используется предопределенный реестр и общий контракт.

public class MyActivity extends AppCompatActivity{

   ...

    /**
     * Activity callback API.
     */
    // https://developer.android.com/training/basics/intents/result
    private ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),

            new ActivityResultCallback<ActivityResult>() {

                @Override
                public void onActivityResult(ActivityResult result) {
                    switch (result.getResultCode()) {
                        case Activity.RESULT_OK:
                            Intent intent = result.getData();
                            // Handle the Intent
                            Toast.makeText(MyActivity.this, "Activity returned ok", Toast.LENGTH_SHORT).show();
                            break;
                        case Activity.RESULT_CANCELED:
                            Toast.makeText(MyActivity.this, "Activity canceled", Toast.LENGTH_SHORT).show();
                            break;
                    }
                }
            });

До нового API у вас было:

btn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MyActivity .this, EditActivity.class);
                startActivityForResult(intent, Constants.INTENT_EDIT_REQUEST_CODE);
            }
        });

Вы можете заметить, что код запроса теперь генерируется (и сохраняется) структурой Google. Ваш код становится:

 btn.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MyActivity .this, EditActivity.class);
                    mStartForResult.launch(intent);
                }
            });
person Zhar    schedule 06.12.2020

Это очень распространенная проблема на Android.

Его можно разбить на три части

  1. Начать действие B (происходит в действии A)
  2. Установить запрошенные данные (происходит в действии B)
  3. Получение запрошенных данных (происходит в действии A)
  1. startActivity B
Intent i = new Intent(A.this, B.class);
startActivity(i);
  1. Установить запрошенные данные

В этой части вы решаете, хотите ли вы отправлять данные обратно или нет, когда происходит определенное событие.

Например: в действии B есть EditText и две кнопки b1, b2. Нажатие на кнопку b1 отправляет данные обратно в действие A. Нажатие на кнопку b2 не отправляет никаких данных.

Отправка данных

b1......clickListener
{
    Intent resultIntent = new Intent();
    resultIntent.putExtra("Your_key", "Your_value");
    setResult(RES_CODE_A, resultIntent);
    finish();
}

Не отправляются данные

b2......clickListener
{
   setResult(RES_CODE_B, new Intent());
   finish();
}

Пользователь нажимает кнопку "Назад"

По умолчанию результат устанавливается с кодом ответа Activity.RESULT_CANCEL.

  1. Получить результат

Для этого переопределить метод onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RES_CODE_A) {

       // b1 was clicked
       String x = data.getStringExtra("RES_CODE_A");

    }
    else if(resultCode == RES_CODE_B){

       // b2 was clicked
    }
    else{
       // The back button was clicked
    }
}
person Rohit Singh    schedule 22.12.2017

В вашей основной деятельности

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(R.id.takeCam).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent=new Intent(getApplicationContext(),TakePhotoActivity.class);
            intent.putExtra("Mode","Take");
            startActivity(intent);
        }
    });
    findViewById(R.id.selectGal).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent=new Intent(getApplicationContext(),TakePhotoActivity.class);
            intent.putExtra("Mode","Gallery");
            startActivity(intent);
        }
    });
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

}

Во втором действии для отображения

private static final int CAMERA_REQUEST = 1888;
private ImageView imageView;
private static final int MY_CAMERA_PERMISSION_CODE = 100;
private static final int PICK_PHOTO_FOR_AVATAR = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_take_photo);

    imageView=findViewById(R.id.imageView);

    if(getIntent().getStringExtra("Mode").equals("Gallery"))
    {
        pickImage();
    }
    else {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_PERMISSION_CODE);
            } else {
                Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivityForResult(cameraIntent, CAMERA_REQUEST);
            }
        }
    }
}
public void pickImage() {
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType("image/*");
    startActivityForResult(intent, PICK_PHOTO_FOR_AVATAR);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == MY_CAMERA_PERMISSION_CODE)
    {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
        {
            Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
            startActivityForResult(cameraIntent, CAMERA_REQUEST);
        }
        else
        {
            Toast.makeText(this, "Camera Permission Denied..", Toast.LENGTH_LONG).show();
        }
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) {
        Bitmap photo = (Bitmap) data.getExtras().get("data");
        imageView.setImageBitmap(photo);
    }
        if (requestCode == PICK_PHOTO_FOR_AVATAR && resultCode == Activity.RESULT_OK) {
            if (data == null) {
                Log.d("ABC","No Such Image Selected");
                return;
            }
            try {
                Uri selectedData=data.getData();
                Log.d("ABC","Image Pick-Up");
                imageView.setImageURI(selectedData);
                InputStream inputStream = getApplicationContext().getContentResolver().openInputStream(selectedData);
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                Bitmap bmp=MediaStore.Images.Media.getBitmap(getContentResolver(),selectedData);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch(IOException e){

            }
    }
}
person Sagar    schedule 15.08.2020

Вам нужно переопределить Activity.onActivityResult ():

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_CODE_ONE) {

       String a = data.getStringExtra("RESULT_CODE_ONE");

    }
    else if(resultCode == RESULT_CODE_TWO){

       // b was clicked
    }
    else{

    }
}
person Nitesh Dev Kunwar    schedule 24.04.2019
comment
Спасибо за ответ, а чем ваш ответ отличается от одобренного? - person Hesam; 18.08.2020