Skip to content
Snippets Groups Projects
Commit e8c2c6e0 authored by Ghazi Akmal Fauzan's avatar Ghazi Akmal Fauzan
Browse files

feat: insert transaction from scan

parent d3a260f0
No related merge requests found
...@@ -2,6 +2,7 @@ package com.example.nerbos.fragments.scan ...@@ -2,6 +2,7 @@ package com.example.nerbos.fragments.scan
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
...@@ -18,6 +19,7 @@ import android.widget.ImageButton ...@@ -18,6 +19,7 @@ import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
...@@ -28,9 +30,13 @@ import androidx.camera.lifecycle.ProcessCameraProvider ...@@ -28,9 +30,13 @@ import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.example.nerbos.R 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.service.Authentication
import com.example.nerbos.viewmodel.TransactionViewModel
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
...@@ -38,19 +44,31 @@ import java.util.concurrent.Executors ...@@ -38,19 +44,31 @@ import java.util.concurrent.Executors
import okhttp3.* import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody
import org.json.JSONException
import org.json.JSONObject
class ScanFragment : Fragment() { class ScanFragment : Fragment() {
private lateinit var previewView: PreviewView private lateinit var previewView: PreviewView
private lateinit var cameraExecutor: ExecutorService private lateinit var cameraExecutor: ExecutorService
private lateinit var imageCapture: ImageCapture private lateinit var imageCapture: ImageCapture
private var fragmentContext: Context? = null
private val requestCameraPermissionCode = Manifest.permission.CAMERA private val requestCameraPermissionCode = Manifest.permission.CAMERA
private val uploadURL: String by lazy { private val uploadURL: String by lazy {
requireContext().getString(R.string.backend_api_scan) 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( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
...@@ -175,14 +193,15 @@ class ScanFragment : Fragment() { ...@@ -175,14 +193,15 @@ class ScanFragment : Fragment() {
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageFile.outputStream()) imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageFile.outputStream())
uploadImageToServer(imageFile) { success, message -> uploadImageToServer(imageFile) { success, message ->
if (success) { if (success) {
showToastOnUIThread("Image uploaded successfully") showToastOnUIThread(message.toString())
} else { } else {
showToastOnUIThread(message ?: "Failed to upload image. Please try again later.") showToastOnUIThread(message.toString())
} }
} }
dialog.dismiss() dialog.dismiss()
} }
.setNegativeButton("No") { dialog, _ -> .setNegativeButton("No") { dialog, _ ->
showToastOnUIThread("Scan cancelled")
dialog.dismiss() dialog.dismiss()
} }
.setView(ImageView(requireContext()).apply { .setView(ImageView(requireContext()).apply {
...@@ -190,44 +209,181 @@ class ScanFragment : Fragment() { ...@@ -190,44 +209,181 @@ class ScanFragment : Fragment() {
adjustViewBounds = true adjustViewBounds = true
}) })
.show() .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 { } else {
Toast.makeText(requireContext(), "Failed to load image", Toast.LENGTH_SHORT).show() 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) { private fun uploadImageToServer(imageFile: File, callback: (Boolean, String?) -> Unit) {
val client = OkHttpClient() showTransactionTypeDialog { selectedTransactionType ->
val authentication = Authentication(requireContext())
val requestBody = MultipartBody.Builder() val token = authentication.getToken()
.setType(MultipartBody.FORM) val client = OkHttpClient()
.addFormDataPart("file", imageFile.name, imageFile.asRequestBody("image/*".toMediaTypeOrNull()))
.build() val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
val authentication = Authentication(requireContext()) .addFormDataPart("file", imageFile.name, imageFile.asRequestBody("image/*".toMediaTypeOrNull()))
val token = authentication.getToken() .build()
val request = Request.Builder() val request = Request.Builder()
.url(uploadURL) .url(uploadURL)
.header("Authorization", "Bearer $token") .header("Authorization", "Bearer $token")
.post(requestBody) .post(requestBody)
.build() .build()
client.newCall(request).enqueue(object : Callback { client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) { if (response.isSuccessful) {
showToastOnUIThread("Image uploaded successfully") // Parse the response body to extract transaction items and calculate total nominal
callback(true, "Image uploaded successfully") val responseBody = response.body?.string()
} else { val totalNominal = calculateTotalNominal(responseBody)
showToastOnUIThread("Failed to upload image. Please try again later.") val itemDetails = getItemsFromResponse(responseBody)
callback(false, "Failed to upload image. Please try again later.")
// 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) { private fun calculateTotalNominal(responseBody: String?): Float? {
showToastOnUIThread("Failed to upload image: ${e.message}") var totalNominal: Float? = null
callback(false, e.message) 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) { private fun showToastOnUIThread(message: String) {
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment