From 9bee6bf9c89375099ea14b0c5418a80dbfe35429 Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Thu, 11 Sep 2025 00:55:45 +0300 Subject: [PATCH 1/9] Implement task 1.1 --- app/src/main/AndroidManifest.xml | 6 ++ .../activities/EditProfileActivity.kt | 84 ++++++++++++++++++- app/src/main/res/values/strings.xml | 5 ++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e3d3319b..9db02e32 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,12 @@ + + + + + currentItemsWhichInChooseImageMethodAlertDialog = which + } + .setPositiveButton( + R.string.alert_dialog_ok, + ::onPositiveChooseImageMethodAlertDialogListenerClick + ) + .setNegativeButton(R.string.aler_dialog_canceled) { _, _ -> } + .create() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit_profile) imageView = findViewById(R.id.imageview_photo) + imageView.setOnClickListener { chooseImageMethodAlertDialog.show() } findViewById(R.id.toolbar).apply { inflateMenu(R.menu.menu) @@ -24,12 +56,14 @@ class EditProfileActivity : AppCompatActivity() { openSenderApp() true } + else -> false } } } } + /** * Используйте этот метод чтобы отобразить картинку полученную из медиатеки в ImageView */ @@ -41,4 +75,52 @@ class EditProfileActivity : AppCompatActivity() { private fun openSenderApp() { TODO("В качестве реализации метода отправьте неявный Intent чтобы поделиться профилем. В качестве extras передайте заполненные строки и картинку") } -} \ No newline at end of file + + private fun handleCameraPermission(granted: Boolean) { + when { + granted -> { + Toast.makeText(this, "Получили доступ к камере", Toast.LENGTH_SHORT).show() + imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + } + + !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> { + // Пользователь еще раз запросил разрешение на использование камеры после отмены + // → покажите Rationale Dialog, и объясните зачем вам камера + + Toast.makeText( + this, + "Нам нужен доступ к камере чтобы следить за тобой ночью", + Toast.LENGTH_LONG + ).show() + + } + + else -> { + // Пользователь нажал не разрешать (однократно) + Toast.makeText(this, "Потом попробуешь еще раз", Toast.LENGTH_SHORT).show() + } + } + } + + /** + * Получает ресурс drawable по его идентификатору. + * + * @param imageId идентификатор ресурса drawable для получения + * @return ресурс drawable, связанный с данным идентификатором, или null, если не найден + */ + private fun getDrawableById(imageId: Int) = + AppCompatResources.getDrawable(this, imageId) + + fun onPositiveChooseImageMethodAlertDialogListenerClick(dialog: DialogInterface?, which: Int) { + //create foto + if (currentItemsWhichInChooseImageMethodAlertDialog == 0) permissionCamera.launch( + Manifest.permission.CAMERA + ) + //choose_foto + if (currentItemsWhichInChooseImageMethodAlertDialog == 1) { + //todo + } + + } + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f57cd4c2..5cbca9d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,9 @@ Activities Отправить + Выберите фото + Назад + Выбрать фото + Сделать фото + OK \ No newline at end of file From 3b9896481c9c2a7ece8e37889e820c9e41ae04d5 Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Thu, 11 Sep 2025 23:54:36 +0300 Subject: [PATCH 2/9] Implement task 1 --- .../activities/EditProfileActivity.kt | 110 ++++++++++++------ .../main/res/values/alert_dialog_strings.xml | 7 ++ .../res/values/rationale_dialog_strings.xml | 8 ++ app/src/main/res/values/strings.xml | 6 - 4 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 app/src/main/res/values/alert_dialog_strings.xml create mode 100644 app/src/main/res/values/rationale_dialog_strings.xml diff --git a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt index d22a34bd..20d594f2 100644 --- a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt +++ b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt @@ -2,9 +2,11 @@ package otus.gpb.homework.activities import android.Manifest import android.content.DialogInterface +import android.content.Intent import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle +import android.provider.Settings import android.widget.ImageView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts @@ -14,6 +16,10 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder +/** + * Полезная статья по разрешениям [shouldShowRequestPermissionRationale] и + * Result API: [Habr Article](https://habr.com/ru/companies/e-legion/articles/545934/) + */ class EditProfileActivity : AppCompatActivity() { private lateinit var imageView: ImageView @@ -23,30 +29,64 @@ class EditProfileActivity : AppCompatActivity() { ::handleCameraPermission ) - private val chooseImageMethodAlertDialogItems = arrayOf( - resources.getString(R.string.aler_dialog_create_foto), - resources.getString(R.string.aler_dialog_choose_foto) - ) - private var currentItemsWhichInChooseImageMethodAlertDialog = -1 + private val launcherSettings = registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { _ -> + if (isCameraForbidden()) + requirementDialog.show() + } + + + private var currentItemsWhichChooseImageMethodDialog = -1 - val chooseImageMethodAlertDialog: AlertDialog = + private val chooseImageMethodDialogItems by lazy { + arrayOf( + resources.getString(R.string.choose_dialog_create_photo), + resources.getString(R.string.choose_dialog_choose_photo) + ) + } + val chooseImageMethodDialog: AlertDialog by lazy { MaterialAlertDialogBuilder(this) - .setTitle(R.string.aler_dialog_title) - .setSingleChoiceItems(chooseImageMethodAlertDialogItems, -1) { _, which -> - currentItemsWhichInChooseImageMethodAlertDialog = which + .setTitle(R.string.choose_dialog_title) + .setSingleChoiceItems(chooseImageMethodDialogItems, -1) { _, which -> + currentItemsWhichChooseImageMethodDialog = which } .setPositiveButton( - R.string.alert_dialog_ok, - ::onPositiveChooseImageMethodAlertDialogListenerClick + R.string.choose_dialog_ok, + ::onPositiveChooseImageMethodDialogListenerClick ) - .setNegativeButton(R.string.aler_dialog_canceled) { _, _ -> } + .setNegativeButton(R.string.choose_dialog_canceled) { dialog, _ -> + currentItemsWhichChooseImageMethodDialog = -1 + dialog.dismiss() + } + .setOnDismissListener { + currentItemsWhichChooseImageMethodDialog = -1 + } + .create() + } + + val rationaleDialog: AlertDialog by lazy { + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.rationale_dialog_title)) + .setMessage(getString(R.string.rationale_dialog_message)) + .setPositiveButton(R.string.rationale_dialog_button_ok, ::startSettingsActivity) + .setNegativeButton(R.string.rationale_dialog_button_canceled) { _, _ -> } .create() + } + val requirementDialog: AlertDialog by lazy { + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.rationale_dialog_title)) + .setMessage(getString(R.string.rationale_dialog_message)) + .setPositiveButton(R.string.rationale_dialog_button_ok, ::startSettingsActivity) + .create() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit_profile) imageView = findViewById(R.id.imageview_photo) - imageView.setOnClickListener { chooseImageMethodAlertDialog.show() } + imageView.setOnClickListener { chooseImageMethodDialog.show() } findViewById(R.id.toolbar).apply { inflateMenu(R.menu.menu) @@ -79,29 +119,24 @@ class EditProfileActivity : AppCompatActivity() { private fun handleCameraPermission(granted: Boolean) { when { granted -> { - Toast.makeText(this, "Получили доступ к камере", Toast.LENGTH_SHORT).show() imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + Toast.makeText(this, "Доступ к камере предоставлен", Toast.LENGTH_SHORT).show() } - - !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> { - // Пользователь еще раз запросил разрешение на использование камеры после отмены - // → покажите Rationale Dialog, и объясните зачем вам камера - - Toast.makeText( - this, - "Нам нужен доступ к камере чтобы следить за тобой ночью", - Toast.LENGTH_LONG - ).show() - + isCameraForbidden() -> { + // доступ к камере запрещен, пользователь поставил галочку Don't ask again. + rationaleDialog.show() } - else -> { - // Пользователь нажал не разрешать (однократно) + // доступ к камере запрещен, пользователь отклонил запрос Toast.makeText(this, "Потом попробуешь еще раз", Toast.LENGTH_SHORT).show() } } } + + private fun isCameraForbidden(): Boolean = + !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) + /** * Получает ресурс drawable по его идентификатору. * @@ -111,16 +146,21 @@ class EditProfileActivity : AppCompatActivity() { private fun getDrawableById(imageId: Int) = AppCompatResources.getDrawable(this, imageId) - fun onPositiveChooseImageMethodAlertDialogListenerClick(dialog: DialogInterface?, which: Int) { - //create foto - if (currentItemsWhichInChooseImageMethodAlertDialog == 0) permissionCamera.launch( - Manifest.permission.CAMERA - ) - //choose_foto - if (currentItemsWhichInChooseImageMethodAlertDialog == 1) { - //todo + fun startSettingsActivity(dialog: DialogInterface?, which: Int) { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + // если в новом потоке, то ResultApi не дожидается результат +// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } + launcherSettings.launch(intent) + } + fun onPositiveChooseImageMethodDialogListenerClick(dialog: DialogInterface?, which: Int) { + when (currentItemsWhichChooseImageMethodDialog) { + 0 -> permissionCamera.launch(Manifest.permission.CAMERA) + 1 -> print("todo") //todo + } + dialog?.dismiss() } } diff --git a/app/src/main/res/values/alert_dialog_strings.xml b/app/src/main/res/values/alert_dialog_strings.xml new file mode 100644 index 00000000..a69bb25c --- /dev/null +++ b/app/src/main/res/values/alert_dialog_strings.xml @@ -0,0 +1,7 @@ + + Выберите фото + Отмена + Выбрать фото + Сделать фото + OK + \ No newline at end of file diff --git a/app/src/main/res/values/rationale_dialog_strings.xml b/app/src/main/res/values/rationale_dialog_strings.xml new file mode 100644 index 00000000..aa3fde90 --- /dev/null +++ b/app/src/main/res/values/rationale_dialog_strings.xml @@ -0,0 +1,8 @@ + + Отправить + Дать доступ + Отмена + Необходимо разрешение + Для того, чтобы снять фотографию, необходимо разрешение на использование камеры + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5cbca9d0..08ea3c61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,9 +1,3 @@ Activities - Отправить - Выберите фото - Назад - Выбрать фото - Сделать фото - OK \ No newline at end of file From 82fb5a40f6fa0bdecaa8c84305d98ae094e10e3d Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Fri, 12 Sep 2025 23:08:33 +0300 Subject: [PATCH 3/9] task 1 bug fix --- .../activities/EditProfileActivity.kt | 160 ++++++++++++------ 1 file changed, 106 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt index 20d594f2..d3534f4c 100644 --- a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt +++ b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt @@ -7,6 +7,7 @@ import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle import android.provider.Settings +import android.util.Log import android.widget.ImageView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts @@ -24,69 +25,97 @@ class EditProfileActivity : AppCompatActivity() { private lateinit var imageView: ImageView - private val permissionCamera = registerForActivityResult( + private val permissionCameraFromChooseImageDialog = registerForActivityResult( ActivityResultContracts.RequestPermission(), - ::handleCameraPermission + ::handleCameraPermissionFromChooseImageDialog + ) + + private val permissionCameraAfterSettingsActivity = registerForActivityResult( + ActivityResultContracts.RequestPermission(), + ::handleCameraPermissionAfterSettingsActivity ) private val launcherSettings = registerForActivityResult( ActivityResultContracts.StartActivityForResult(), ) { _ -> - if (isCameraForbidden()) - requirementDialog.show() + Log.d("debug_granted", "Callback launcherSettings.") + permissionCameraAfterSettingsActivity.launch(Manifest.permission.CAMERA) } + private var currentItemsWhichChooseImageDialog = -1 - private var currentItemsWhichChooseImageMethodDialog = -1 - - private val chooseImageMethodDialogItems by lazy { + private val chooseImageDialogItems by lazy { arrayOf( resources.getString(R.string.choose_dialog_create_photo), resources.getString(R.string.choose_dialog_choose_photo) ) } - val chooseImageMethodDialog: AlertDialog by lazy { - MaterialAlertDialogBuilder(this) - .setTitle(R.string.choose_dialog_title) - .setSingleChoiceItems(chooseImageMethodDialogItems, -1) { _, which -> - currentItemsWhichChooseImageMethodDialog = which - } - .setPositiveButton( - R.string.choose_dialog_ok, - ::onPositiveChooseImageMethodDialogListenerClick - ) - .setNegativeButton(R.string.choose_dialog_canceled) { dialog, _ -> - currentItemsWhichChooseImageMethodDialog = -1 - dialog.dismiss() - } + + //region dialogs + val chooseImageDialog: AlertDialog + get() { + Log.d("debug_granted", "chooseImageDialog getter.") + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.choose_dialog_title) + .setSingleChoiceItems(chooseImageDialogItems, -1) { _, which -> + currentItemsWhichChooseImageDialog = which + } + .setPositiveButton( + R.string.choose_dialog_ok, + ::onPositiveChooseImageDialogListenerClick + ) + .setNegativeButton(R.string.choose_dialog_canceled) { dialog, _ -> + Log.d("debug_granted", "chooseImageDialog onNegativeButtonClickListener.") + dialog.dismiss() + } .setOnDismissListener { - currentItemsWhichChooseImageMethodDialog = -1 + Log.d("debug_granted", "chooseImageDialog onDismissListener.") + currentItemsWhichChooseImageDialog = -1 + } + .create() } - .create() - } - val rationaleDialog: AlertDialog by lazy { - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.rationale_dialog_title)) - .setMessage(getString(R.string.rationale_dialog_message)) - .setPositiveButton(R.string.rationale_dialog_button_ok, ::startSettingsActivity) - .setNegativeButton(R.string.rationale_dialog_button_canceled) { _, _ -> } - .create() - } - val requirementDialog: AlertDialog by lazy { - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.rationale_dialog_title)) - .setMessage(getString(R.string.rationale_dialog_message)) - .setPositiveButton(R.string.rationale_dialog_button_ok, ::startSettingsActivity) - .create() - } + val rationaleDialog: AlertDialog + get() { + Log.d("debug_granted", "rationaleDialog getter.") + return MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.rationale_dialog_title)) + .setMessage(getString(R.string.rationale_dialog_message)) + .setPositiveButton( + R.string.rationale_dialog_button_ok, + ::rationaleDialogInClickListener + ) + .setNegativeButton(R.string.rationale_dialog_button_canceled) { _, _ -> + Log.d("debug_granted", "rationaleDialog onNegativeButtonClickListener.") + } + .setOnDismissListener { + Log.d("debug_granted", "rationaleDialog onDismissListener.") + } + .create() + } + val requirementDialog: AlertDialog + get() { + Log.d("debug_granted", "requirementDialog getter.") + return MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.rationale_dialog_title)) + .setMessage(getString(R.string.rationale_dialog_message)) + .setPositiveButton( + R.string.rationale_dialog_button_ok, + ::requirementDialogInClickListener + ) + .setOnDismissListener { + Log.d("debug_granted", "requirementDialog onDismissListener.") + } + .create() + } + //endregion override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit_profile) imageView = findViewById(R.id.imageview_photo) - imageView.setOnClickListener { chooseImageMethodDialog.show() } + imageView.setOnClickListener { chooseImageDialog.show() } findViewById(R.id.toolbar).apply { inflateMenu(R.menu.menu) @@ -103,7 +132,6 @@ class EditProfileActivity : AppCompatActivity() { } } - /** * Используйте этот метод чтобы отобразить картинку полученную из медиатеки в ImageView */ @@ -116,23 +144,32 @@ class EditProfileActivity : AppCompatActivity() { TODO("В качестве реализации метода отправьте неявный Intent чтобы поделиться профилем. В качестве extras передайте заполненные строки и картинку") } - private fun handleCameraPermission(granted: Boolean) { + private fun handleCameraPermissionFromChooseImageDialog(granted: Boolean) { when { granted -> { imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + Log.d("debug_granted", "handleCameraPermissionFromChooseImageDialog: granted") Toast.makeText(this, "Доступ к камере предоставлен", Toast.LENGTH_SHORT).show() } + // доступ к камере запрещен, пользователь поставил галочку Don't ask again. isCameraForbidden() -> { - // доступ к камере запрещен, пользователь поставил галочку Don't ask again. + Log.d("debug_granted", "camera forbidden") rationaleDialog.show() } + // доступ к камере запрещен, пользователь отклонил запрос else -> { - // доступ к камере запрещен, пользователь отклонил запрос + Log.d("debug_granted", "camera denied") Toast.makeText(this, "Потом попробуешь еще раз", Toast.LENGTH_SHORT).show() } } } + private fun handleCameraPermissionAfterSettingsActivity(granted: Boolean) { + Log.d("debug_granted", "handleCameraPermissionAfterSettingsActivity: granted: $granted") + if (!granted) requirementDialog.show() + else imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + + } private fun isCameraForbidden(): Boolean = !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) @@ -146,21 +183,36 @@ class EditProfileActivity : AppCompatActivity() { private fun getDrawableById(imageId: Int) = AppCompatResources.getDrawable(this, imageId) - fun startSettingsActivity(dialog: DialogInterface?, which: Int) { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", packageName, null) - // если в новом потоке, то ResultApi не дожидается результат -// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - launcherSettings.launch(intent) + //region listeners + private fun rationaleDialogInClickListener(dialog: DialogInterface?, which: Int) { + Log.d("debug_granted", "rationaleDialogInClickListener") + startSettingsActivity() + dialog?.dismiss() } - fun onPositiveChooseImageMethodDialogListenerClick(dialog: DialogInterface?, which: Int) { - when (currentItemsWhichChooseImageMethodDialog) { - 0 -> permissionCamera.launch(Manifest.permission.CAMERA) + private fun requirementDialogInClickListener(dialog: DialogInterface?, which: Int) { + Log.d("debug_granted", "requirementDialogInClickListener") + startSettingsActivity() + dialog?.dismiss() + } + + fun onPositiveChooseImageDialogListenerClick(dialog: DialogInterface?, which: Int) { + Log.d("debug_granted", "onPositiveChooseImageDialogListenerClick") + when (currentItemsWhichChooseImageDialog) { + 0 -> permissionCameraFromChooseImageDialog.launch(Manifest.permission.CAMERA) 1 -> print("todo") //todo } dialog?.dismiss() } + //endregion + fun startSettingsActivity() { + Log.d("debug_granted", "startSettingsActivity") + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + // если в новом потоке, то ResultApi не дожидается результат +// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + launcherSettings.launch(intent) + } } From 202903322b91ff966d60f4bd3e92969668a46dad Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Fri, 12 Sep 2025 23:20:26 +0300 Subject: [PATCH 4/9] Implement task 2 --- .../activities/EditProfileActivity.kt | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt index d3534f4c..4d6e4248 100644 --- a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt +++ b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt @@ -10,6 +10,7 @@ import android.provider.Settings import android.util.Log import android.widget.ImageView import android.widget.Toast +import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity @@ -35,11 +36,13 @@ class EditProfileActivity : AppCompatActivity() { ::handleCameraPermissionAfterSettingsActivity ) - private val launcherSettings = registerForActivityResult( - ActivityResultContracts.StartActivityForResult(), - ) { _ -> - Log.d("debug_granted", "Callback launcherSettings.") - permissionCameraAfterSettingsActivity.launch(Manifest.permission.CAMERA) + private val takePictureUri = registerForActivityResult( + ActivityResultContracts.GetContent() + ) { image: Uri? -> + Log.d("debug_granted", "Callback takePictureUri. uri: $image") + image?.let { + populateImage(image) + } } private var currentItemsWhichChooseImageDialog = -1 @@ -144,6 +147,7 @@ class EditProfileActivity : AppCompatActivity() { TODO("В качестве реализации метода отправьте неявный Intent чтобы поделиться профилем. В качестве extras передайте заполненные строки и картинку") } + //region handlers private fun handleCameraPermissionFromChooseImageDialog(granted: Boolean) { when { granted -> { @@ -168,8 +172,18 @@ class EditProfileActivity : AppCompatActivity() { Log.d("debug_granted", "handleCameraPermissionAfterSettingsActivity: granted: $granted") if (!granted) requirementDialog.show() else imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + } + private fun handleLaunchSettings(activityResult: ActivityResult) { + Log.d("debug_granted", "Callback launcherSettings.") + permissionCameraAfterSettingsActivity.launch(Manifest.permission.CAMERA) } + //endregion + + private val launcherSettings = registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ::handleLaunchSettings + ) private fun isCameraForbidden(): Boolean = !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) @@ -200,7 +214,7 @@ class EditProfileActivity : AppCompatActivity() { Log.d("debug_granted", "onPositiveChooseImageDialogListenerClick") when (currentItemsWhichChooseImageDialog) { 0 -> permissionCameraFromChooseImageDialog.launch(Manifest.permission.CAMERA) - 1 -> print("todo") //todo + 1 -> takePictureUri.launch("image/*") } dialog?.dismiss() } From 4dde09f0225453ac3e376b2fd47c86311681b1ec Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Fri, 12 Sep 2025 23:53:40 +0300 Subject: [PATCH 5/9] task 1. bit map photo as avatar image --- .../activities/EditProfileActivity.kt | 153 ++++++++++-------- 1 file changed, 88 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt index 4d6e4248..eecd4eab 100644 --- a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt +++ b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt @@ -3,6 +3,7 @@ package otus.gpb.homework.activities import android.Manifest import android.content.DialogInterface import android.content.Intent +import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle @@ -12,6 +13,7 @@ import android.widget.ImageView import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.result.launch import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources @@ -25,7 +27,36 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder class EditProfileActivity : AppCompatActivity() { private lateinit var imageView: ImageView + private var currentItemsWhichChooseImageDialog = -1 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_edit_profile) + imageView = findViewById(R.id.imageview_photo) + imageView.setOnClickListener { chooseImageDialog.show() } + + findViewById(R.id.toolbar).apply { + inflateMenu(R.menu.menu) + setOnMenuItemClickListener { + when (it.itemId) { + R.id.send_item -> { + openSenderApp() + true + } + + else -> false + } + } + } + } + private val chooseImageDialogItems by lazy { + arrayOf( + resources.getString(R.string.choose_dialog_create_photo), + resources.getString(R.string.choose_dialog_choose_photo) + ) + } + //region activity results private val permissionCameraFromChooseImageDialog = registerForActivityResult( ActivityResultContracts.RequestPermission(), ::handleCameraPermissionFromChooseImageDialog @@ -37,22 +68,20 @@ class EditProfileActivity : AppCompatActivity() { ) private val takePictureUri = registerForActivityResult( - ActivityResultContracts.GetContent() - ) { image: Uri? -> - Log.d("debug_granted", "Callback takePictureUri. uri: $image") - image?.let { - populateImage(image) - } - } + ActivityResultContracts.GetContent(), + ::handleTakePictureUri + ) - private var currentItemsWhichChooseImageDialog = -1 + private val takePictures = registerForActivityResult( + ActivityResultContracts.TakePicturePreview(), + ::handleTakePictures + ) - private val chooseImageDialogItems by lazy { - arrayOf( - resources.getString(R.string.choose_dialog_create_photo), - resources.getString(R.string.choose_dialog_choose_photo) - ) - } + private val launcherSettings = registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ::handleLaunchSettings + ) + //endregion //region dialogs val chooseImageDialog: AlertDialog @@ -114,44 +143,11 @@ class EditProfileActivity : AppCompatActivity() { } //endregion - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_edit_profile) - imageView = findViewById(R.id.imageview_photo) - imageView.setOnClickListener { chooseImageDialog.show() } - - findViewById(R.id.toolbar).apply { - inflateMenu(R.menu.menu) - setOnMenuItemClickListener { - when (it.itemId) { - R.id.send_item -> { - openSenderApp() - true - } - - else -> false - } - } - } - } - - /** - * Используйте этот метод чтобы отобразить картинку полученную из медиатеки в ImageView - */ - private fun populateImage(uri: Uri) { - val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)) - imageView.setImageBitmap(bitmap) - } - - private fun openSenderApp() { - TODO("В качестве реализации метода отправьте неявный Intent чтобы поделиться профилем. В качестве extras передайте заполненные строки и картинку") - } - //region handlers private fun handleCameraPermissionFromChooseImageDialog(granted: Boolean) { when { granted -> { - imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + setCameraImage() Log.d("debug_granted", "handleCameraPermissionFromChooseImageDialog: granted") Toast.makeText(this, "Доступ к камере предоставлен", Toast.LENGTH_SHORT).show() } @@ -171,31 +167,28 @@ class EditProfileActivity : AppCompatActivity() { private fun handleCameraPermissionAfterSettingsActivity(granted: Boolean) { Log.d("debug_granted", "handleCameraPermissionAfterSettingsActivity: granted: $granted") if (!granted) requirementDialog.show() - else imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + else setCameraImage() } + private fun handleLaunchSettings(activityResult: ActivityResult) { Log.d("debug_granted", "Callback launcherSettings.") permissionCameraAfterSettingsActivity.launch(Manifest.permission.CAMERA) } - //endregion - private val launcherSettings = registerForActivityResult( - ActivityResultContracts.StartActivityForResult(), - ::handleLaunchSettings - ) - - private fun isCameraForbidden(): Boolean = - !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) + private fun handleTakePictures(image: Bitmap?) { + Log.d("debug_granted", "Callback takePictures. image: $image") + if (image != null) imageView.setImageBitmap(image) + else imageView.setImageDrawable(getDrawableById(R.drawable.cat)) + } - /** - * Получает ресурс drawable по его идентификатору. - * - * @param imageId идентификатор ресурса drawable для получения - * @return ресурс drawable, связанный с данным идентификатором, или null, если не найден - */ - private fun getDrawableById(imageId: Int) = - AppCompatResources.getDrawable(this, imageId) + private fun handleTakePictureUri(image: Uri?) { + Log.d("debug_granted", "Callback takePictureUri. uri: $image") + image?.let { + populateImage(image) + } + } + //endregion //region listeners private fun rationaleDialogInClickListener(dialog: DialogInterface?, which: Int) { @@ -220,6 +213,36 @@ class EditProfileActivity : AppCompatActivity() { } //endregion + // region image settings + /** + * Используйте этот метод чтобы отобразить картинку полученную из медиатеки в ImageView + */ + private fun populateImage(uri: Uri) { + val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)) + imageView.setImageBitmap(bitmap) + } + + private fun setCameraImage() { + takePictures.launch() + } + + /** + * Получает ресурс drawable по его идентификатору. + * + * @param imageId идентификатор ресурса drawable для получения + * @return ресурс drawable, связанный с данным идентификатором, или null, если не найден + */ + private fun getDrawableById(imageId: Int) = + AppCompatResources.getDrawable(this, imageId) + //endregion + + private fun openSenderApp() { + TODO("В качестве реализации метода отправьте неявный Intent чтобы поделиться профилем. В качестве extras передайте заполненные строки и картинку") + } + + private fun isCameraForbidden(): Boolean = + !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) + fun startSettingsActivity() { Log.d("debug_granted", "startSettingsActivity") val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { From ee5e4480566dc2141ac0ba45f2eacb6c08110148 Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Sat, 13 Sep 2025 23:54:15 +0300 Subject: [PATCH 6/9] task 3. subtask 1-4 --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 4 +- .../activities/EditProfileActivity.kt | 58 ++++++++++++++--- .../homework/activities/FillFormActivity.kt | 59 +++++++++++++++++ .../contracts/ContractForFillFormActivity.kt | 40 ++++++++++++ .../main/java/otus/gpb/homework/model/User.kt | 11 ++++ .../main/res/layout/activity_edit_profile.xml | 22 +++---- .../main/res/layout/activity_fill_form.xml | 63 +++++++++++++++++++ app/src/main/res/values/strings.xml | 7 +++ 9 files changed, 245 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/otus/gpb/homework/activities/FillFormActivity.kt create mode 100644 app/src/main/java/otus/gpb/homework/contracts/ContractForFillFormActivity.kt create mode 100644 app/src/main/java/otus/gpb/homework/model/User.kt create mode 100644 app/src/main/res/layout/activity_fill_form.xml diff --git a/app/build.gradle b/app/build.gradle index 57688408..18aeb310 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id("io.gitlab.arturbosch.detekt") + id("kotlin-parcelize") } android { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9db02e32..64469ed8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,9 @@ android:supportsRtl="true" android:theme="@style/Theme.Activities" tools:targetApi="31"> - + diff --git a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt index eecd4eab..40b36ddf 100644 --- a/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt +++ b/app/src/main/java/otus/gpb/homework/activities/EditProfileActivity.kt @@ -9,7 +9,9 @@ import android.net.Uri import android.os.Bundle import android.provider.Settings import android.util.Log +import android.widget.Button import android.widget.ImageView +import android.widget.TextView import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -19,6 +21,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder +import otus.gpb.homework.contracts.ContractForFillFormActivity +import otus.gpb.homework.model.User /** * Полезная статья по разрешениям [shouldShowRequestPermissionRationale] и @@ -26,14 +30,33 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder */ class EditProfileActivity : AppCompatActivity() { - private lateinit var imageView: ImageView + //region elementsById + private val chooseImageDialogItems by lazy { + arrayOf( + resources.getString(R.string.choose_dialog_create_photo), + resources.getString(R.string.choose_dialog_choose_photo) + ) + } + + private val imageView by lazy { findViewById(R.id.imageview_photo) } + + private val editProfileButton by lazy { findViewById