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