diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..5af6101 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +OTUS Location+Maps HW \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..a74f29f --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3e76c3d..83fbd65 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,6 +4,13 @@ plugins { id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' } +// Настройка secrets-gradle-plugin для использования API ключа из local.properties +secrets { + propertiesFileName = 'local.properties' + defaultPropertiesFileName = 'local.properties' + ignoreList.add("keyToIgnore") +} + android { namespace 'com.sample.otuslocationmapshw' compileSdk 35 @@ -15,6 +22,7 @@ android { versionCode 1 versionName "1.0" + buildConfigField "String", "MAPS_API_KEY", "\"${project.findProperty('MAPS_API_KEY') ?: ''}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -33,6 +41,7 @@ android { } buildFeatures { viewBinding true + buildConfig true } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7a1883f..4cfc455 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,20 @@ + + + + + + + + + + + android:value="${MAPS_API_KEY}" /> + val exifInterface = ExifInterface(lastFile) + val location = locationDataUtils.getLocationFromExif(exifInterface) + val lastPoint = LatLng(location.latitude, location.longitude) + // Анимация перемещения камеры к точке с зумом 15 + map.animateCamera(CameraUpdateFactory.newLatLngZoom(lastPoint, 15f)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/sample/otuslocationmapshw/camera/CameraActivity.kt b/app/src/main/java/com/sample/otuslocationmapshw/camera/CameraActivity.kt index 076ade5..0886eae 100644 --- a/app/src/main/java/com/sample/otuslocationmapshw/camera/CameraActivity.kt +++ b/app/src/main/java/com/sample/otuslocationmapshw/camera/CameraActivity.kt @@ -14,6 +14,7 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.app.ActivityCompat @@ -58,21 +59,24 @@ class CameraActivity : AppCompatActivity() { fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) cameraProviderFuture = ProcessCameraProvider.getInstance(this) - // TODO("Получить экземпляр SensorManager") - // TODO("Добавить проверку на наличие датчика акселерометра и присвоить значение tiltSensor") - tiltSensor = TODO("Get tilt sensor") + // Получение экземпляра SensorManager для работы с датчиками + sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager + // Получение датчика акселерометра + tiltSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) cameraProviderFuture.addListener({ cameraProvider = cameraProviderFuture.get() }, ContextCompat.getMainExecutor(this)) sensorEventListener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent) { - val tilt = event.values[2] - binding.errorTextView.visibility = if (abs(tilt) > 2) View.VISIBLE else View.GONE + // Отслеживание угла наклона по оси X (values[0]) + val tiltX = event.values[0] + // Если телефон наклонен слишком сильно (больше 2 единиц), показываем предупреждение + binding.errorTextView.visibility = if (abs(tiltX) > 2) View.VISIBLE else View.GONE } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { - //nothing to do + // Ничего не делаем при изменении точности датчика } } @@ -81,9 +85,23 @@ class CameraActivity : AppCompatActivity() { } } - // TODO("Подписаться на получение событий обновления датчика") + override fun onResume() { + super.onResume() + // Подписываемся на получение событий обновления датчика акселерометра + tiltSensor?.let { + sensorManager.registerListener( + sensorEventListener, + it, + SensorManager.SENSOR_DELAY_NORMAL + ) + } + } - // TODO("Остановить получение событий от датчика") + override fun onPause() { + super.onPause() + // Отписываемся от получения событий от датчика для экономии ресурсов + sensorManager.unregisterListener(sensorEventListener) + } override fun onRequestPermissionsResult( requestCode: Int, @@ -117,19 +135,68 @@ class CameraActivity : AppCompatActivity() { } val filePath = folderPath + SimpleDateFormat(FILENAME_FORMAT, Locale.getDefault()).format(Date()) - // TODO("4. Добавить установку местоположения в метаданные фото") + // Создание метаданных с местоположением для фото + val metadata = ImageCapture.Metadata().apply { + // Установка местоположения в метаданные фото + location?.let { + this.location = it + } + } + + // Создание настроек для сохранения фото с метаданными val outputFileOptions = ImageCapture.OutputFileOptions.Builder(File(filePath)) + .setMetadata(metadata) // Установка метаданных с местоположением .build() - // TODO("Добавить вызов CameraX для фото") - // TODO("Вывести Toast о том, что фото успешно сохранено и закрыть текущее активити c указанием кода результата SUCCESS_RESULT_CODE") - // imageCapture... + // Получение главного executor для выполнения callback-ов + val executor = ContextCompat.getMainExecutor(this) + + // Вызов CameraX для создания фото + imageCapture.takePicture( + outputFileOptions, + executor, + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(output: ImageCapture.OutputFileResults) { + // Успешное сохранение фото + Toast.makeText( + this@CameraActivity, + "Фото успешно сохранено!", + Toast.LENGTH_SHORT + ).show() + // Установка результата работы активити + setResult(SUCCESS_RESULT_CODE) + // Закрытие активити + finish() + } + + override fun onError(exception: ImageCaptureException) { + // Обработка ошибки при сохранении фото + Log.e(TAG, "Photo capture failed: ${exception.message}", exception) + Toast.makeText( + this@CameraActivity, + "Ошибка при сохранении фото: ${exception.message}", + Toast.LENGTH_LONG + ).show() + } + } + ) } } @SuppressLint("MissingPermission") private fun getLastLocation(callback: (location: Location?) -> Unit) { - // TODO("Добавить получение местоположения от fusedLocationClient и передать результат в callback после получения") + // Получение текущего местоположения через FusedLocationProviderClient + // Используем getLastLocation() для получения последнего известного местоположения + fusedLocationClient.lastLocation + .addOnSuccessListener { location: Location? -> + // Передаем полученное местоположение в callback + callback.invoke(location) + } + .addOnFailureListener { exception -> + Log.e(TAG, "Error getting location", exception) + // В случае ошибки передаем null + callback.invoke(null) + } } private fun startCamera() { @@ -168,10 +235,12 @@ class CameraActivity : AppCompatActivity() { private const val TAG = "CameraXApp" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private const val REQUEST_CODE_PERMISSIONS = 10 - // TODO("Указать набор требуемых разрешений") - private val REQUIRED_PERMISSIONS: Array = mutableListOf( - // TODO("Добавить требуемые разрешения") - ).toTypedArray() + // Набор требуемых разрешений для работы с камерой и местоположением + private val REQUIRED_PERMISSIONS: Array = arrayOf( + android.Manifest.permission.CAMERA, + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_COARSE_LOCATION + ) const val SUCCESS_RESULT_CODE = 15 }