diff --git a/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt b/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt index a7bf474abba94624133338ce218ad87295d7e584..803015586a080eda094a614661e240d63a0eebe1 100644 --- a/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt +++ b/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt @@ -1,11 +1,15 @@ package com.example.bondoyap.service.api +import com.example.bondoyap.service.api.data.BillResponse import com.example.bondoyap.service.api.data.LoginRequest import com.example.bondoyap.service.api.data.LoginResponse import com.example.bondoyap.service.api.data.TokenResponse +import okhttp3.MultipartBody import retrofit2.Call import retrofit2.http.Body +import retrofit2.http.Multipart import retrofit2.http.POST +import retrofit2.http.Part interface ApiService { @POST("auth/login") @@ -19,6 +23,8 @@ interface ApiService { ): Call<TokenResponse> @POST("bill/upload") - fun uploadPicture( - ) + @Multipart + fun getBill( + @Part photoPart : MultipartBody.Part + ) : Call<BillResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/BillResponse.kt b/app/src/main/java/com/example/bondoyap/service/api/data/BillResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..098c5e658bc2c42e7acb89deea7e3ff7514ea188 --- /dev/null +++ b/app/src/main/java/com/example/bondoyap/service/api/data/BillResponse.kt @@ -0,0 +1,5 @@ +package com.example.bondoyap.service.api.data + +data class BillResponse( + val items : Items +) diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/Item.kt b/app/src/main/java/com/example/bondoyap/service/api/data/Item.kt new file mode 100644 index 0000000000000000000000000000000000000000..cd01390409cf0b82f1638f536e0d814e988817be --- /dev/null +++ b/app/src/main/java/com/example/bondoyap/service/api/data/Item.kt @@ -0,0 +1,9 @@ +package com.example.bondoyap.service.api.data +import com.squareup.moshi.Json + +data class Item( + val name : String, + @Json(name = "qty") + val quantity : Int, + val price : Double +) diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/Items.kt b/app/src/main/java/com/example/bondoyap/service/api/data/Items.kt new file mode 100644 index 0000000000000000000000000000000000000000..6c779b17b1baebb2337f106ad94a51398ea47458 --- /dev/null +++ b/app/src/main/java/com/example/bondoyap/service/api/data/Items.kt @@ -0,0 +1,6 @@ +package com.example.bondoyap.service.api.data +import com.squareup.moshi.Json + +data class Items( + val items : List<Item> +) diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt index d9e0d20d8e345b4bddfaf94b8b7a405678feb1cd..9a528d6704daab30b96f38b3855d4c9e959d8830 100644 --- a/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt +++ b/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt @@ -2,23 +2,35 @@ package com.example.bondoyap.ui.scanner import android.Manifest import android.app.Activity +import android.content.ContentResolver +import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.provider.MediaStore import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.content.Intent -import android.provider.MediaStore import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.widget.AppCompatImageView 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.content.ContextCompat import androidx.fragment.app.Fragment import com.example.bondoyap.databinding.FragmentScannerBinding +import com.example.bondoyap.service.api.ApiClient +import com.example.bondoyap.service.api.data.BillResponse +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.io.File +import java.io.FileOutputStream class ScannerFragment : Fragment() { @@ -26,10 +38,11 @@ class ScannerFragment : Fragment() { // onDestroyView. private var _binding: FragmentScannerBinding? = null private val binding get() = _binding!! - + // Camera private var isBackCamera = true private var frozenPreview: Boolean = false + private var cameraImageFile: File? = null private lateinit var imageCapture: ImageCapture private lateinit var cameraLauncher: ActivityResultLauncher<String> @@ -37,15 +50,24 @@ class ScannerFragment : Fragment() { private lateinit var changeImage: ActivityResultLauncher<Intent> private lateinit var pickImageLauncher: ActivityResultLauncher<String> + // Upload + private lateinit var cacheDir: File + private lateinit var contentResolver: ContentResolver + private lateinit var apiClient: ApiClient + override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View { _binding = FragmentScannerBinding.inflate(inflater, container, false) val root: View = binding.root + cacheDir = requireContext().cacheDir + contentResolver = requireContext().contentResolver + apiClient = ApiClient() + cameraLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted -> @@ -54,27 +76,41 @@ class ScannerFragment : Fragment() { } } + changeImage = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + if (it.resultCode == Activity.RESULT_OK) { + try { + val data = it.data + val imgUri = data?.data ?: throw Exception("Image Uri is null") + Log.d("Image Input", "Image Selected with URI: $imgUri") + + // Copying image from external directory to cache + val tempFile = File.createTempFile("Gallery_Image", ".jpg", cacheDir) + val inputStream = contentResolver.openInputStream(imgUri) + val outputStream = FileOutputStream(tempFile) + inputStream?.use { input -> + outputStream.use { output -> + input.copyTo(output) + } + } + + uploadPhoto(tempFile) + } catch (e: Exception) { + Log.e("Image Input", "Image Input Failed:", e) + } + } + } + + val pickImageIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI) pickImageLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted -> if (isGranted) { - val pickImageIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI) changeImage.launch(pickImageIntent) } } - changeImage = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { - if (it.resultCode == Activity.RESULT_OK) { - val data = it.data - val imgUri = data?.data - val selectedImage = AppCompatImageView(requireContext()) - selectedImage.setImageURI(imgUri) - Log.d("Image Input", "Image Selected with URI: $imgUri") - } - } - imageCapture = ImageCapture.Builder().build() cameraLauncher.launch(Manifest.permission.CAMERA) @@ -90,6 +126,11 @@ class ScannerFragment : Fragment() { pickImageLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) } + binding.uploadButton.isClickable = false + binding.uploadButton.setOnClickListener { + cameraImageFile?.let { it1 -> uploadPhoto(it1) } + } + return root } @@ -102,29 +143,51 @@ class ScannerFragment : Fragment() { val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() - - val preview = Preview.Builder().build().also { - mPreview -> + + val preview = Preview.Builder().build().also { mPreview -> if (!frozenPreview) { mPreview.setSurfaceProvider(binding.previewView.surfaceProvider) } else { mPreview.setSurfaceProvider(null) } } - - imageCapture = ImageCapture.Builder().build() - + + val imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + .build() + val cameraSelector = if (isBackCamera) { CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA } - + try { cameraProvider?.unbindAll() - cameraProvider?.bindToLifecycle(this, cameraSelector, preview) + cameraProvider?.bindToLifecycle(this, cameraSelector, preview, imageCapture) + + val tempFile = File.createTempFile("Camera_Image", ".jpg", cacheDir) + val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build() + imageCapture.takePicture(outputOptions, + ContextCompat.getMainExecutor(requireContext()), + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { + val uriImg = Uri.fromFile(tempFile) + cameraImageFile = tempFile + Log.d("CameraX", "Image captured: $uriImg") + } + + override fun onError(exception: ImageCaptureException) { + Log.e( + "CameraX", + "Error capturing image: ${exception.message}", + exception + ) + } + } + ) } catch (e: Exception) { - Log.d("CameraX", "startCamera Failed:", e) + Log.e("CameraX", "Starting Camera Failed:", e) } }, ContextCompat.getMainExecutor(requireContext())) } @@ -138,9 +201,37 @@ class ScannerFragment : Fragment() { frozenPreview = !frozenPreview if (!frozenPreview) { binding.captureButton.text = "Capture" + binding.uploadButton.isClickable = false } else { binding.captureButton.text = "Retake" + binding.uploadButton.isClickable = true } cameraLauncher.launch(Manifest.permission.CAMERA) } + + private fun uploadPhoto(photo: File) { + try { + val requestFile = photo.asRequestBody("image/*".toMediaTypeOrNull()) + val requestBody = MultipartBody.Part.createFormData("file", photo.name, requestFile) + val apiCall = apiClient.getApiService(requireContext()).getBill(requestBody) + apiCall.enqueue(object : Callback<BillResponse> { + override fun onResponse( + call: Call<BillResponse>, response: Response<BillResponse> + ) { + if (response.isSuccessful) { + val billResponse = response.body() + Log.d("Bill Upload", "Server Response: $billResponse") + } else { + Log.e("Bill Upload", "Error: ${response.code()}") + } + } + + override fun onFailure(call: Call<BillResponse>, t: Throwable) { + Log.e("Bill Upload", "Failed to upload bill", t) + } + }) + } catch (e: Exception) { + Log.e("Bill Upload", "Bill Upload Failed:", e) + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_scanner.xml b/app/src/main/res/layout/fragment_scanner.xml index 459c75552df3d47d5a6f0ad32f6b4d49c303d723..7a8caec4843e0b957a2e481a334b248225c4614a 100644 --- a/app/src/main/res/layout/fragment_scanner.xml +++ b/app/src/main/res/layout/fragment_scanner.xml @@ -38,4 +38,14 @@ app:layout_constraintStart_toEndOf="@+id/capture_button" app:layout_constraintEnd_toEndOf="parent" /> + + + <Button + android:id="@+id/upload_button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/upload_button" + app:layout_constraintTop_toBottomOf="@id/capture_button" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintLeft_toLeftOf="parent" /> </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 1cb99fecd39d2bdf28e3566ab9cc8abbe55ece62..885484cbc10fb521a9e44d0ad5dc3396934c7f60 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,4 +24,5 @@ <string name="capture_button">capture</string> <string name="switch_camera_button">switch camera</string> <string name="gallery_button">gallery</string> + <string name="upload_button">upload</string> </resources> \ No newline at end of file