diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b58c9af6f55082849eae8879220ecfb053a4d09..5358e5240b825a51ab4f0a03b896ed45accdc1a1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -59,6 +59,24 @@ dependencies { implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.okhttp3:logging-interceptor:4.2.0") implementation("androidx.security:security-crypto:1.1.0-alpha03") + + + // Scan Feature + // CameraX core library using the camera2 implementation + val camerax_version = "1.3.2" + // The following line is optional, as the core library is included indirectly by camera-camera2 + implementation("androidx.camera:camera-core:${camerax_version}") + implementation("androidx.camera:camera-camera2:${camerax_version}") + // If you want to additionally use the CameraX Lifecycle library + implementation("androidx.camera:camera-lifecycle:${camerax_version}") + // If you want to additionally use the CameraX VideoCapture library + implementation("androidx.camera:camera-video:${camerax_version}") + // If you want to additionally use the CameraX View class + implementation("androidx.camera:camera-view:${camerax_version}") + // If you want to additionally use the CameraX Extensions library + implementation("androidx.camera:camera-extensions:${camerax_version}") + implementation("androidx.camera:camera-view:${camerax_version}") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f79b02642e970e47f63117dc8684329efaaa4df6..c7b067810ce14775390971a4815c900f1cbef22b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,12 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> + xmlns:tools="http://schemas.android.com/tools"> + <uses-feature android:name="android.hardware.camera.any" /> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" + android:maxSdkVersion="32" /> + <application android:allowBackup="true" diff --git a/app/src/main/java/com/example/tubespbd/ui/scan/CameraHandler.kt b/app/src/main/java/com/example/tubespbd/ui/scan/CameraHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..582e89f52d9ea4a8355a69d030f68a1287395e9d --- /dev/null +++ b/app/src/main/java/com/example/tubespbd/ui/scan/CameraHandler.kt @@ -0,0 +1,92 @@ +import android.content.Context +import android.net.Uri +import android.util.Log +import androidx.camera.core.CameraSelector +import androidx.camera.core.Preview +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import com.example.tubespbd.R +import com.example.tubespbd.databinding.FragmentScanBinding +import com.google.common.util.concurrent.ListenableFuture +import java.io.File +import java.text.SimpleDateFormat +import java.util.Locale + +class CameraHandler( + private val context: Context, + private val lifecycleOwner: LifecycleOwner, + private val binding: FragmentScanBinding) { + + private lateinit var cameraProviderFuture : ListenableFuture<ProcessCameraProvider> + private lateinit var imageCapture: ImageCapture + + fun startCamera() { + // Initialize Preview + cameraProviderFuture = ProcessCameraProvider.getInstance(context) + cameraProviderFuture.addListener({ + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + bindPreview(cameraProvider) + }, ContextCompat.getMainExecutor(context)) + + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + // Add more configuration options here if needed + .build() + + } + + private fun bindPreview(cameraProvider: ProcessCameraProvider) { + val previewView = binding.previewView + val preview : Preview = Preview.Builder().build() + preview.setSurfaceProvider(previewView.surfaceProvider) + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + try { + cameraProvider.unbindAll() + cameraProvider.bindToLifecycle( + lifecycleOwner, + cameraSelector, + preview, + imageCapture) + } catch (exc: Exception) { + Log.e("CameraHandler", "Use case binding failed", exc) + } + } + + fun takePicture() { + val outputFileOptions = ImageCapture.OutputFileOptions.Builder(getOutputFile()).build() + imageCapture.takePicture( + outputFileOptions, + ContextCompat.getMainExecutor(context), + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { + val savedUri = outputFileResults.savedUri ?: Uri.fromFile(getOutputFile()) + Log.d("CameraHandler", "Photo capture succeeded: $savedUri") + } + + override fun onError(exception: ImageCaptureException) { + Log.e("CameraHandler", "Photo capture failed: ${exception.message}", exception) + } + } + ) + } + + private fun getOutputFile(): File { + val mediaDir = context.externalMediaDirs.firstOrNull()?.let { + File(it, context.resources.getString(R.string.app_name)).apply { mkdirs() } + } + val outputDirectory = if (mediaDir != null && mediaDir.exists()) mediaDir else context.filesDir + val fileName = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US) + .format(System.currentTimeMillis()) + ".jpg" + return File(outputDirectory, fileName) + } + +// private fun showPicture(): File { +// +// } + +} diff --git a/app/src/main/java/com/example/tubespbd/ui/scan/ScanFragment.kt b/app/src/main/java/com/example/tubespbd/ui/scan/ScanFragment.kt index 09f1f16117954647e1c648373e4ab83642956b6d..792dc4a5c14372f8d6f972b6fd2db663cd518ff6 100644 --- a/app/src/main/java/com/example/tubespbd/ui/scan/ScanFragment.kt +++ b/app/src/main/java/com/example/tubespbd/ui/scan/ScanFragment.kt @@ -1,34 +1,89 @@ package com.example.tubespbd.ui.scan +import CameraHandler +import android.Manifest +import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView +import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.example.tubespbd.databinding.FragmentScanBinding +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat class ScanFragment : Fragment() { private var _binding: FragmentScanBinding? = null + private lateinit var cameraHandler: CameraHandler private val binding get() = _binding!! + + // On Create Function override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val scanViewModel = - ViewModelProvider(this).get(ScanViewModel::class.java) + ViewModelProvider(this)[ScanViewModel::class.java] _binding = FragmentScanBinding.inflate(inflater, container, false) + _binding?.let { + cameraHandler = CameraHandler(requireContext(), viewLifecycleOwner, it) + } val root: View = binding.root - val textView: TextView = binding.textScan +// val textView: TextView = binding.textScan scanViewModel.text.observe(viewLifecycleOwner){ - textView.text = it +// textView.text = it } return root } + // On View Created Function, for permission after view is made + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (allPermissionsGranted()) { + cameraHandler.startCamera() + binding.captureButton.setOnClickListener { + cameraHandler.takePicture() + } + } else { + ActivityCompat.requestPermissions( + requireActivity(), REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS + ) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array<String>, + grantResults: IntArray + ) { + if (requestCode == REQUEST_CODE_PERMISSIONS) { + if (allPermissionsGranted()) { + cameraHandler.startCamera() + } else { + Toast.makeText( + context, + "Permissions not granted by the user.", + Toast.LENGTH_SHORT + ).show() + } + } + } + + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission( + requireContext(), it) == PackageManager.PERMISSION_GRANTED + } + + companion object { + private const val REQUEST_CODE_PERMISSIONS = 10 + private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/java/com/example/tubespbd/ui/scan/ScanViewModel.kt b/app/src/main/java/com/example/tubespbd/ui/scan/ScanViewModel.kt index 6c61fd896fc754769bf422c009b8bd86f0ab7c46..ec4863c5d8e2c118c9ed7f44a610c0fba9b6a92f 100644 --- a/app/src/main/java/com/example/tubespbd/ui/scan/ScanViewModel.kt +++ b/app/src/main/java/com/example/tubespbd/ui/scan/ScanViewModel.kt @@ -4,8 +4,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class ScanViewModel : ViewModel(){ + private val _text = MutableLiveData<String>().apply { - value = "This is scan fragment" + value = "Pindai strukmu!" } val text: LiveData<String> = _text } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml index 948a2d96b9c2d31f5df9066c1a2868a2e8d378fa..af3096c38cf59e773b122b9d2697b39928de04fd 100644 --- a/app/src/main/res/layout/fragment_scan.xml +++ b/app/src/main/res/layout/fragment_scan.xml @@ -6,18 +6,27 @@ android:layout_height="match_parent" tools:context=".ui.scan.ScanFragment"> - <TextView - android:id="@+id/text_scan" - android:layout_width="match_parent" + + <Button + android:id="@+id/capture_button" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" - android:textAlignment="center" - android:textSize="20sp" - app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginTop="50dp" + android:text="@string/camera_capture_text" + app:layout_constraintBottom_toBottomOf="@+id/previewView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + <androidx.camera.view.PreviewView + android:id="@+id/previewView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + app:layout_constraintTop_toTopOf="parent" + tools:layout_editor_absoluteX="0dp"> + + </androidx.camera.view.PreviewView> + + </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/popup_scan_image.xml b/app/src/main/res/layout/popup_scan_image.xml new file mode 100644 index 0000000000000000000000000000000000000000..62efb8a799d438ef0305d16e556129496d942c09 --- /dev/null +++ b/app/src/main/res/layout/popup_scan_image.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/cancel_popup_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Cancel" + tools:layout_editor_absoluteX="42dp" + tools:layout_editor_absoluteY="621dp" /> + + <Button + android:id="@+id/save_popup_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="save" + tools:layout_editor_absoluteX="198dp" + tools:layout_editor_absoluteY="621dp" /> + + <TextView + android:id="@+id/result_popup_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="hasil" + android:textSize="34sp" + tools:layout_editor_absoluteX="135dp" + tools:layout_editor_absoluteY="358dp" /> +</androidx.constraintlayout.widget.ConstraintLayout> \ 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 3f23b7b5412e56016cd394c05fb5f0bd268c83a5..0ac31e5c48ded74d9ad873b5197c6be6c3a27a30 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,4 +15,5 @@ <string name="incorrect_login">Your login credentials are not correct</string> <string name="incorrect_email_format">Incorrect email format!</string> <string name="keep_me_logged_in">Keep me logged in</string> + <string name="camera_capture_text">Capture</string> </resources> \ No newline at end of file