diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f7742da461a76e7a067d93ef8f1fd89dfba1c2ff..c920a39773c94b53581a624ea8e0fc3b4e502593 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,11 +50,13 @@ android { } dependencies { - + implementation("androidx.camera:camera-camera2:1.3.2") + implementation("androidx.camera:camera-lifecycle:1.3.2") + implementation("androidx.camera:camera-core:1.3.2") implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-compose:1.8.2") - implementation(platform("androidx.compose:compose-bom:2023.08.00")) + implementation(platform("androidx.compose:compose-bom:2024.03.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") @@ -62,23 +64,24 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("com.google.android.material:material:1.11.0") implementation("androidx.mediarouter:mediarouter:1.7.0") + implementation("androidx.camera:camera-view:1.3.2") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) + androidTestImplementation(platform("androidx.compose:compose-bom:2024.03.00")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") // Retrofit - implementation("com.google.code.gson:gson:2.9.0") + implementation("com.google.code.gson:gson:2.10.1") implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0") // Room - implementation("androidx.room:room-runtime:2.5.0") - annotationProcessor("androidx.room:room-compiler:2.5.0") - implementation("androidx.room:room-ktx:2.5.0") + implementation("androidx.room:room-runtime:2.6.1") + annotationProcessor("androidx.room:room-compiler:2.6.1") + implementation("androidx.room:room-ktx:2.6.1") implementation ("net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3") } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fc1529e4b1145f92a5f15e2dbdf63217d14820cb..a9e24f1f3df4384c3c4ee59181a780c3c8b12e6c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools"> + xmlns:tools="http://schemas.android.com/tools" + package="com.example.bondoman"> + + <uses-feature + android:name="android.hardware.camera" + android:required="false" /> + <uses-permission android:name="android.permission.CAMERA"/> <application android:allowBackup="true" @@ -17,7 +23,6 @@ android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> @@ -30,6 +35,17 @@ android:name=".TransaksiBaruActivity" android:exported="true"> </activity> + + <provider + android:authorities="com.example.bondoman.fileProvider" + android:name="androidx.core.content.FileProvider" + android:exported="false" + android:grantUriPermissions="true"> + + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_path"/> + </provider> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/app/src/main/java/com/example/bondoman/MainActivity.kt b/app/src/main/java/com/example/bondoman/MainActivity.kt index 81b69df84863027d8ab88be4d3e34b42d24af82a..23ed3181f5e8b00604d8e56737d8471c98b1f6f4 100644 --- a/app/src/main/java/com/example/bondoman/MainActivity.kt +++ b/app/src/main/java/com/example/bondoman/MainActivity.kt @@ -2,15 +2,9 @@ package com.example.bondoman import SettingsFragment import android.annotation.SuppressLint -import android.content.Intent import android.os.Bundle -import android.text.TextUtils -import android.widget.Button -import android.widget.EditText -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import android.view.MenuItem import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import com.google.android.material.bottomnavigation.BottomNavigationView @@ -36,7 +30,7 @@ class MainActivity : AppCompatActivity() { R.id.scan -> { item.setIcon(R.drawable.scanner_bold) - // Perform actions for scanner fragment call + replaceFragment(CameraFragment(), "Scanner") true } diff --git a/app/src/main/java/com/example/bondoman/fragment_scanner.kt b/app/src/main/java/com/example/bondoman/fragment_scanner.kt new file mode 100644 index 0000000000000000000000000000000000000000..1394d5240ba8a1457704b1f7f080066110748eee --- /dev/null +++ b/app/src/main/java/com/example/bondoman/fragment_scanner.kt @@ -0,0 +1,199 @@ +package com.example.bondoman + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.Toast +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.camera.view.PreviewView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner +import com.google.android.material.floatingactionbutton.FloatingActionButton +import java.io.File +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +@Suppress("DEPRECATION") +class CameraFragment : Fragment() { + + private lateinit var cameraExecutor: ExecutorService + private lateinit var imageCapture: ImageCapture + private lateinit var viewFinder: PreviewView + private lateinit var capturedImageView: ImageView + private lateinit var retakeButton: Button + private lateinit var galleryButton : Button + private lateinit var saveButton: Button + private lateinit var currentPhotoPath: String + + @SuppressLint("MissingInflatedId") + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_scanner, container, false) + viewFinder = view.findViewById(R.id.viewFinder) + capturedImageView = view.findViewById(R.id.capturedImageView) + retakeButton = view.findViewById(R.id.retakeButton) + saveButton = view.findViewById(R.id.saveButton) + galleryButton = view.findViewById(R.id.galleryButton) + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + cameraExecutor = Executors.newSingleThreadExecutor() + + galleryButton.setOnClickListener { + if (allPermissionsGranted()) { + openGallery() + } else { + ActivityCompat.requestPermissions( + requireActivity(), + REQUIRED_PERMISSIONS, + REQUEST_CODE_PERMISSIONS + ) + } + } + + // Request camera permissions + if (allPermissionsGranted()) { + startCamera() + } else { + ActivityCompat.requestPermissions( + requireActivity(), + REQUIRED_PERMISSIONS, + REQUEST_CODE_PERMISSIONS + ) + } + + val roundButton: FloatingActionButton = view.findViewById(R.id.round_button) + roundButton.setOnClickListener { + takePhoto() + } + + retakeButton.setOnClickListener { + capturedImageView.visibility = View.GONE + retakeButton.visibility = View.GONE + saveButton.visibility = View.GONE + viewFinder.visibility = View.VISIBLE + startCamera() + } + + saveButton.setOnClickListener { + // Save the photo + // Implement your saving logic here + Toast.makeText(requireContext(), "Photo saved successfully!", Toast.LENGTH_SHORT).show() + // After saving, you can navigate to the next screen or perform any other action + } + } + + private val REQUEST_CODE_GALLERY = 1001 + + private fun openGallery() { + val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) + startActivityForResult(intent, REQUEST_CODE_GALLERY) + showCapturedImage() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CODE_GALLERY && resultCode == Activity.RESULT_OK) { + data?.data?.let { uri -> + capturedImageView.setImageURI(uri) + capturedImageView.visibility = View.VISIBLE + } + } + } + + private fun allPermissionsGranted() = + REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission( + requireContext(), it + ) == PackageManager.PERMISSION_GRANTED + } + + private fun startCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + cameraProviderFuture.addListener({ + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + + val preview = Preview.Builder().build().also { + it.setSurfaceProvider(viewFinder.surfaceProvider) + } + + imageCapture = ImageCapture.Builder() + .build() + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + try { + cameraProvider.unbindAll() + + cameraProvider.bindToLifecycle( + this as LifecycleOwner, cameraSelector, preview, imageCapture + ) + + } catch (exc: Exception) { + Toast.makeText(requireContext(), "Camera initialization failed", Toast.LENGTH_SHORT).show() + } + + }, ContextCompat.getMainExecutor(requireContext())) + } + + private fun takePhoto() { + val imageCapture = imageCapture + + val photoFile = File( + requireContext().externalMediaDirs.firstOrNull(), + "${System.currentTimeMillis()}.jpg" + ) + + val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() + + imageCapture.takePicture( + outputOptions, ContextCompat.getMainExecutor(requireContext()), object : ImageCapture.OnImageSavedCallback { + override fun onError(exc: ImageCaptureException) { + Toast.makeText(requireContext(), "Photo capture failed: ${exc.message}", Toast.LENGTH_SHORT).show() + } + + override fun onImageSaved(output: ImageCapture.OutputFileResults) { + currentPhotoPath = photoFile.absolutePath + showCapturedImage() + } + }) + } + + private fun showCapturedImage() { + viewFinder.visibility = View.GONE + capturedImageView.visibility = View.VISIBLE + capturedImageView.setImageURI(Uri.fromFile(File(currentPhotoPath))) + retakeButton.visibility = View.VISIBLE + saveButton.visibility = View.VISIBLE + } + + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + } + + companion object { + private const val REQUEST_CODE_PERMISSIONS = 10 + private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) + } +} diff --git a/app/src/main/res/drawable/ic_gallery.xml b/app/src/main/res/drawable/ic_gallery.xml new file mode 100644 index 0000000000000000000000000000000000000000..961a5ea835ed60b45dea93788535512f8bd54a95 --- /dev/null +++ b/app/src/main/res/drawable/ic_gallery.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- custom_drawable.xml --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:drawable="@drawable/ic_gallery_raw" + android:gravity="center" + android:scaleType="fitXY" /> +</layer-list> diff --git a/app/src/main/res/drawable/ic_gallery_raw.jpg b/app/src/main/res/drawable/ic_gallery_raw.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0074bcea251ec7d3acec8be2b4875b16b10473b7 Binary files /dev/null and b/app/src/main/res/drawable/ic_gallery_raw.jpg differ diff --git a/app/src/main/res/layout/fragment_scanner.xml b/app/src/main/res/layout/fragment_scanner.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f5bdf3e73a146c47d39e37d2220e3b105dac61b --- /dev/null +++ b/app/src/main/res/layout/fragment_scanner.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.camera.view.PreviewView + android:id="@+id/viewFinder" + android:layout_width="match_parent" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintDimensionRatio="H,9:16" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="1.0" + tools:layout_editor_absoluteX="16dp" /> + + <ImageView + android:id="@+id/capturedImageView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:scaleType="fitCenter" /> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/round_button" + android:layout_width="118dp" + android:layout_height="79dp" + android:layout_alignParentBottom="true" + android:layout_centerInParent="true" + android:layout_centerHorizontal="true" + android:layout_marginBottom="52dp" + android:backgroundTint="@color/design_default_color_error" + android:elevation="6dp" + android:tint="@android:color/white" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/retakeButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_alignParentBottom="true" + android:layout_margin="16dp" + android:text="@string/retake" + android:visibility="gone" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="MissingConstraints" /> + + <Button + android:id="@+id/saveButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:layout_margin="16dp" + android:text="@string/save" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="MissingConstraints" /> + + <Button + android:id="@+id/galleryButton" + android:layout_width="70dp" + android:layout_height="70dp" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:layout_marginEnd="25dp" + android:layout_marginBottom="55dp" + android:text="@string/gallery" + android:textSize="10sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + </Button> + + +</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 c949ce0536e5814d0b7091b8107cdca8890ed6d6..56a505bd86dfc4a4871eed13eadc5708d76d77aa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,4 +22,7 @@ <!-- TODO: Remove or change this placeholder text --> <string name="hello_blank_fragment">Hello blank fragment</string> <string name="transaksi_baru">Transaksi Baru</string> + <string name="save">Save</string> + <string name="retake">Retake</string> + <string name="gallery">Gallery</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/xml/file_path.xml b/app/src/main/res/xml/file_path.xml new file mode 100644 index 0000000000000000000000000000000000000000..33cf2e51613379177f142e4101f9c2a77000b120 --- /dev/null +++ b/app/src/main/res/xml/file_path.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + + <files-path + name="camera_photos" + path="." /> + +</paths> \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 0db614e9accc798eda2c925a6fbae22ed846f1d7..a40aa821cac5f297e466fd729ffc4677925a5b15 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,4 @@ plugins { id("com.android.application") version "8.3.0" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false -} \ No newline at end of file +}