From e8c2c6e0655d82bc208ee4c78e344f9120a42a56 Mon Sep 17 00:00:00 2001 From: Ghazi Akmal Fauzan <13521058@std.stei.itb.ac.id> Date: Mon, 1 Apr 2024 10:53:58 +0700 Subject: [PATCH] feat: insert transaction from scan --- .../nerbos/fragments/scan/ScanFragment.kt | 218 +++++++++++++++--- 1 file changed, 187 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/example/nerbos/fragments/scan/ScanFragment.kt b/app/src/main/java/com/example/nerbos/fragments/scan/ScanFragment.kt index 44a497a..0783db7 100644 --- a/app/src/main/java/com/example/nerbos/fragments/scan/ScanFragment.kt +++ b/app/src/main/java/com/example/nerbos/fragments/scan/ScanFragment.kt @@ -2,6 +2,7 @@ package com.example.nerbos.fragments.scan import android.Manifest import android.app.Activity +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap @@ -18,6 +19,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.camera.core.CameraSelector @@ -28,9 +30,13 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.net.toUri import androidx.exifinterface.media.ExifInterface +import androidx.lifecycle.ViewModelProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.example.nerbos.R +import com.example.nerbos.model.Transaction +import com.example.nerbos.model.TransactionCategory import com.example.nerbos.service.Authentication +import com.example.nerbos.viewmodel.TransactionViewModel import java.io.File import java.io.IOException import java.util.concurrent.ExecutorService @@ -38,19 +44,31 @@ import java.util.concurrent.Executors import okhttp3.* import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.asRequestBody +import org.json.JSONException +import org.json.JSONObject class ScanFragment : Fragment() { private lateinit var previewView: PreviewView private lateinit var cameraExecutor: ExecutorService private lateinit var imageCapture: ImageCapture + private var fragmentContext: Context? = null private val requestCameraPermissionCode = Manifest.permission.CAMERA - private val uploadURL: String by lazy { requireContext().getString(R.string.backend_api_scan) } + override fun onAttach(context: Context) { + super.onAttach(context) + fragmentContext = context + } + + override fun onDetach() { + super.onDetach() + fragmentContext = null + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -175,14 +193,15 @@ class ScanFragment : Fragment() { imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageFile.outputStream()) uploadImageToServer(imageFile) { success, message -> if (success) { - showToastOnUIThread("Image uploaded successfully") + showToastOnUIThread(message.toString()) } else { - showToastOnUIThread(message ?: "Failed to upload image. Please try again later.") + showToastOnUIThread(message.toString()) } } dialog.dismiss() } .setNegativeButton("No") { dialog, _ -> + showToastOnUIThread("Scan cancelled") dialog.dismiss() } .setView(ImageView(requireContext()).apply { @@ -190,44 +209,181 @@ class ScanFragment : Fragment() { adjustViewBounds = true }) .show() + .apply { + getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + } } else { Toast.makeText(requireContext(), "Failed to load image", Toast.LENGTH_SHORT).show() } } + private fun showTransactionTypeDialog(callback: (String) -> Unit) { + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("Select Transaction Type") + builder.setMessage("Please select the type of transaction you want to add.") + + builder.setPositiveButton("Income") { dialog, _ -> + // Handle income transaction + callback("Income") + dialog.dismiss() + } + + builder.setNegativeButton("Outcome") { dialog, _ -> + // Handle outcome transaction + callback("Outcome") + dialog.dismiss() + } + + val dialog = builder.create() + dialog.show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + dialog.getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + } + + private fun showTransactionConfirmationDialog(responseBody: String?, selectedTransactionType: String, totalNominal: Float, callback: (Boolean) -> Unit) { + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("Confirm Transaction Details") + + // Parse the JSON response body to extract item details + val itemDetails = StringBuilder() + calculateTotalNominal(responseBody)?.let { + try { + val jsonObject = JSONObject(responseBody.toString()) + val itemsArray = jsonObject.getJSONObject("items").getJSONArray("items") + for (i in 0 until itemsArray.length()) { + val itemObject = itemsArray.getJSONObject(i) + val name = itemObject.getString("name") + val qty = itemObject.getInt("qty") + val price = itemObject.getDouble("price").toFloat() + itemDetails.append("$name: $qty x $price\n") + } + } catch (e: JSONException) { + e.printStackTrace() + } + } + + // Adjust message based on transaction type + val transactionTypeMessage = if (selectedTransactionType == "Income") "income" else "outcome" + val message = "$itemDetails\nTotal Nominal: $totalNominal\n\nThis transaction will be added as an $transactionTypeMessage with location: Bandung.\n\nDo you want to proceed?" + builder.setMessage(message) + + builder.setPositiveButton("Yes") { dialog, _ -> + callback(true) + dialog.dismiss() + } + + builder.setNegativeButton("No") { dialog, _ -> + callback(false) + dialog.dismiss() + } + + val dialog = builder.create() + dialog.show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + dialog.getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + } + private fun uploadImageToServer(imageFile: File, callback: (Boolean, String?) -> Unit) { - val client = OkHttpClient() - - val requestBody = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("file", imageFile.name, imageFile.asRequestBody("image/*".toMediaTypeOrNull())) - .build() - - val authentication = Authentication(requireContext()) - val token = authentication.getToken() - - val request = Request.Builder() - .url(uploadURL) - .header("Authorization", "Bearer $token") - .post(requestBody) - .build() - - client.newCall(request).enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - showToastOnUIThread("Image uploaded successfully") - callback(true, "Image uploaded successfully") - } else { - showToastOnUIThread("Failed to upload image. Please try again later.") - callback(false, "Failed to upload image. Please try again later.") + showTransactionTypeDialog { selectedTransactionType -> + val authentication = Authentication(requireContext()) + val token = authentication.getToken() + val client = OkHttpClient() + + val requestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", imageFile.name, imageFile.asRequestBody("image/*".toMediaTypeOrNull())) + .build() + + val request = Request.Builder() + .url(uploadURL) + .header("Authorization", "Bearer $token") + .post(requestBody) + .build() + + client.newCall(request).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + // Parse the response body to extract transaction items and calculate total nominal + val responseBody = response.body?.string() + val totalNominal = calculateTotalNominal(responseBody) + val itemDetails = getItemsFromResponse(responseBody) + + // Run on UI thread to show transaction confirmation dialog + requireActivity().runOnUiThread { + showTransactionConfirmationDialog(responseBody, selectedTransactionType, totalNominal ?: 0f) { confirmed -> + if (confirmed) { + // Create a single transaction and add it to the database + totalNominal?.let { nominal -> + val transaction = Transaction( + userID = authentication.getNim(), + name = "Scan Transaction $itemDetails", + category = if (selectedTransactionType == "Income") TransactionCategory.INCOME else TransactionCategory.OUTCOME, + nominal = nominal, + location = "Bandung" + ) + addTransactionToDatabase(transaction) + callback(true, "Transaction added successfully") + } ?: run { + callback(false, "Failed to calculate total nominal") + } + } else { + callback(false, "Transaction cancelled") + } + } + } + } else { + callback(false, "Failed to upload image. Please try again later.") + } } + + override fun onFailure(call: Call, e: IOException) { + showToastOnUIThread("Failed to upload image: ${e.message}") + callback(false, e.message) + } + }) + } + } + + private fun getItemsFromResponse(responseBody: String?): String { + val itemDetails = StringBuilder() + try { + val jsonObject = JSONObject(responseBody.toString()) + val itemsArray = jsonObject.getJSONObject("items").getJSONArray("items") + for (i in 0 until itemsArray.length()) { + val itemObject = itemsArray.getJSONObject(i) + val name = itemObject.getString("name") + itemDetails.append("$name ") } + } catch (e: JSONException) { + e.printStackTrace() + } + return itemDetails.toString() + } - override fun onFailure(call: Call, e: IOException) { - showToastOnUIThread("Failed to upload image: ${e.message}") - callback(false, e.message) + private fun calculateTotalNominal(responseBody: String?): Float? { + var totalNominal: Float? = null + try { + val jsonObject = JSONObject(responseBody.toString()) + val itemsArray = jsonObject.getJSONObject("items").getJSONArray("items") + totalNominal = 0f + for (i in 0 until itemsArray.length()) { + val itemObject = itemsArray.getJSONObject(i) + val qty = itemObject.getInt("qty") + val price = itemObject.getDouble("price").toFloat() + totalNominal += qty * price } - }) + } catch (e: JSONException) { + e.printStackTrace() + } + return totalNominal + } + + private fun addTransactionToDatabase(transaction: Transaction) { + val transactionViewModel = ViewModelProvider(requireActivity())[TransactionViewModel::class.java] + transactionViewModel.addTransaction(transaction) } private fun showToastOnUIThread(message: String) { -- GitLab