diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a5035dc94adabcf6c2b2a7dde1185e89da8329aa..ed80158b39dd898b0cc721966de1398880dc2863 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -151,8 +151,7 @@ dependencies { implementation("org.apache.poi:poi:5.2.5") implementation("org.apache.poi:poi-ooxml:5.2.5") - - + implementation("com.androidplot:androidplot-core:1.5.10") } kapt { diff --git a/app/src/main/java/com/example/pbd_jwr/MainActivity.kt b/app/src/main/java/com/example/pbd_jwr/MainActivity.kt index 05270e61b9678fff108ad3faf6d6e4835bc0ad5b..00560396bf89523e192b33c1de981917aa829429 100644 --- a/app/src/main/java/com/example/pbd_jwr/MainActivity.kt +++ b/app/src/main/java/com/example/pbd_jwr/MainActivity.kt @@ -7,23 +7,29 @@ import android.content.SharedPreferences.Editor import android.net.ConnectivityManager import android.os.Bundle import android.content.pm.PackageManager -import android.location.LocationManager import android.widget.Toast import android.Manifest +import android.app.Activity +import androidx.activity.result.contract.ActivityResultContracts import com.google.android.material.bottomnavigation.BottomNavigationView import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.lifecycle.ViewModelProvider import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import com.example.pbd_jwr.backgroundService.JWTValidationService +import com.example.pbd_jwr.data.entity.Transaction import com.example.pbd_jwr.databinding.ActivityMainBinding import com.example.pbd_jwr.encryptedSharedPref.EncryptedSharedPref import com.example.pbd_jwr.network.NetworkCallbackImplementation +import com.example.pbd_jwr.ui.transaction.TransactionViewModel import com.google.android.material.floatingactionbutton.FloatingActionButton +import org.json.JSONObject +import java.util.Date class MainActivity : AppCompatActivity() { @@ -33,6 +39,9 @@ class MainActivity : AppCompatActivity() { private lateinit var connectivityManager: ConnectivityManager private lateinit var networkCallback: NetworkCallbackImplementation + + private lateinit var mTransactionViewModel: TransactionViewModel + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val serviceIntent = Intent(this, JWTValidationService::class.java) @@ -52,7 +61,9 @@ class MainActivity : AppCompatActivity() { } val navView: BottomNavigationView = binding.navView - navView.background=null; + navView.background=null + + mTransactionViewModel = ViewModelProvider(this)[TransactionViewModel::class.java] val navController = findNavController(R.id.nav_host_fragment_activity_main) // Passing each menu ID as a set of Ids because each @@ -68,7 +79,7 @@ class MainActivity : AppCompatActivity() { fab.setOnClickListener { // Start ScanActivity val intent = Intent(this, ScanActivity::class.java) - startActivity(intent) + startScanActivityForResult.launch(intent) } navView.setOnItemSelectedListener { menuItem -> when (menuItem.itemId) { @@ -167,6 +178,36 @@ class MainActivity : AppCompatActivity() { connectivityManager.unregisterNetworkCallback(networkCallback) } + private val startScanActivityForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val data = result.data + val transactionDummyData = data?.getStringExtra("transactionDummyData") + + transactionDummyData?.let { + + val jsonObject = JSONObject(transactionDummyData) + 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 category = "expense" + val price = itemObject.getDouble("price") + val qty = itemObject.getInt("qty") + val amount = qty * price + val latitude = 6.8915 + val longitude = 107.6107 + val location = "Latitude: $latitude, Longitude: $longitude" + val date = Date().time + + mTransactionViewModel.addTransaction(Transaction(userId = 1, title = name, category = category, amount = amount, location = location, date = date)) + } + + + } + } + } + companion object { private const val LOCATION_PERMISSION_REQUEST_CODE = 1001 } diff --git a/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt b/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt index 9939b4a9c7384818a047da762e534d61951c92f4..79129cff2ef8ca932aadcf697690806ca2935d4a 100644 --- a/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt +++ b/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt @@ -7,17 +7,17 @@ import android.provider.MediaStore import android.widget.Button import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity -import com.example.pbd_jwr.R import android.content.pm.PackageManager import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import android.Manifest import android.annotation.SuppressLint +import android.app.Activity +import android.app.AlertDialog import android.content.Context import android.content.SharedPreferences import android.graphics.BitmapFactory import android.net.Uri -import android.os.Build import android.widget.Toast import okhttp3.* import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -25,11 +25,14 @@ import java.io.File import java.io.IOException import android.os.Environment import android.util.Log +import android.view.LayoutInflater +import android.widget.ListView import androidx.core.content.FileProvider import com.example.pbd_jwr.encryptedSharedPref.EncryptedSharedPref +import com.example.pbd_jwr.ui.transaction.TransactionDummyAdapter import okhttp3.RequestBody.Companion.asRequestBody +import org.json.JSONObject import java.io.FileOutputStream -import java.io.InputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -40,6 +43,7 @@ class ScanActivity : AppCompatActivity() { private var imageUri: Uri? = null private var currentPhotoPath: String = "" private lateinit var sharedPreferences: SharedPreferences + var transactionDummyData: String = "" companion object { const val REQUEST_IMAGE_CAPTURE = 1 @@ -48,6 +52,13 @@ class ScanActivity : AppCompatActivity() { const val REQUEST_STORAGE_PERMISSION = 4 } + data class TransactionDummy( + val name: String, + val qty: Int, + val price: Double + ) + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -56,10 +67,15 @@ class ScanActivity : AppCompatActivity() { sharedPreferences = EncryptedSharedPref.create(applicationContext,"login") val scanButton: Button = findViewById(R.id.scanBtn) + val galleryBtn: Button = findViewById(R.id.galleryBtn) val uploadButton: Button = findViewById(R.id.uploadBtn) - val sendButton: Button = findViewById(R.id.sendBtn) + val saveBtn: Button = findViewById(R.id.saveTransactionsBtn) val backBtn: Button = findViewById(R.id.backBtn) + val placeholderImage: ImageView = findViewById(R.id.imageView) + placeholderImage.setImageResource(R.drawable.baseline_insert_photo_24) + addDummyTransactions("{\"items\":{\"items\":[{\"name\":\"none\",\"qty\":0,\"price\":0}]}}") + backBtn.setOnClickListener { finish() } @@ -68,17 +84,22 @@ class ScanActivity : AppCompatActivity() { checkAndRequestPermissions() } - uploadButton.setOnClickListener { + galleryBtn.setOnClickListener { openGallery() } - sendButton.setOnClickListener { + uploadButton.setOnClickListener { if (imageUri != null) { uploadImage(imageUri!!) } else { - Toast.makeText(this, "Tidak ada gambar yang dipilih atau diambil", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "No image selected or taken", Toast.LENGTH_SHORT).show() } } + + + saveBtn.setOnClickListener { + handleSave() + } } private fun checkAndRequestPermissions() { @@ -100,6 +121,34 @@ class ScanActivity : AppCompatActivity() { } } + @SuppressLint("InflateParams") + private fun handleSave() { + if (transactionDummyData == "") { + Toast.makeText(this, "No transactions read", Toast.LENGTH_SHORT).show() + } else { + val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_confirm_transaction, null) + val dialogBuilder = AlertDialog.Builder(this).apply { + setView(dialogView) + setCancelable(true) // Memungkinkan dialog untuk dibatalkan dengan tombol back atau mengetuk di luar + } + val dialog = dialogBuilder.create() + + dialogView.findViewById<Button>(R.id.btnCancel).setOnClickListener { + dialog.dismiss() + } + + dialogView.findViewById<Button>(R.id.btnOkay).setOnClickListener { + val resultIntent = Intent() + resultIntent.putExtra("transactionDummyData", transactionDummyData) + setResult(Activity.RESULT_OK, resultIntent) + dialog.dismiss() + finish() + } + + dialog.show() + } + } + private fun openCamera() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) @@ -130,7 +179,6 @@ class ScanActivity : AppCompatActivity() { } } - private fun startCameraIntent() { Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> takePictureIntent.resolveActivity(packageManager)?.also { @@ -146,14 +194,14 @@ class ScanActivity : AppCompatActivity() { if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { openCamera() } else { - Toast.makeText(this, "Izin penyimpanan diperlukan", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "Storage permission is required", Toast.LENGTH_SHORT).show() } } REQUEST_CAMERA_PERMISSION -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCameraIntent() } else { - Toast.makeText(this, "Izin kamera diperlukan", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "Camera permission is required", Toast.LENGTH_SHORT).show() } } } @@ -165,13 +213,11 @@ class ScanActivity : AppCompatActivity() { startActivityForResult(intent, PICK_IMAGE) } - private fun getBitmapFromUri(uri: Uri, context: Context): Bitmap? { val inputStream = context.contentResolver.openInputStream(uri) return BitmapFactory.decodeStream(inputStream) } - private fun compressAndSaveBitmap(bitmap: Bitmap, context: Context): File { // Membuat file output di direktori cache eksternal val compressedFile = File(context.externalCacheDir, "compressed_${System.currentTimeMillis()}.jpg") @@ -185,13 +231,12 @@ class ScanActivity : AppCompatActivity() { return compressedFile } - @SuppressLint("Recycle") private fun uploadImage(imageUri: Uri) { val token = sharedPreferences.getString("token", null) if (token.isNullOrEmpty()) { runOnUiThread { - Toast.makeText(this, "Token tidak ditemukan. $token.", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "Token not found.", Toast.LENGTH_SHORT).show() } return } @@ -214,13 +259,23 @@ class ScanActivity : AppCompatActivity() { OkHttpClient().newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { - runOnUiThread { Toast.makeText(this@ScanActivity, "Gagal mengirim gambar", Toast.LENGTH_SHORT).show() } + runOnUiThread { Toast.makeText(this@ScanActivity, "Failed to upload image", Toast.LENGTH_SHORT).show() } } override fun onResponse(call: Call, response: Response) { runOnUiThread { if (response.isSuccessful) { - Toast.makeText(this@ScanActivity, "Gambar berhasil diupload", Toast.LENGTH_SHORT).show() + val responseBody = response.body?.string() + + Log.d("Response", "response body: $responseBody") + + if (responseBody != null) { + transactionDummyData = responseBody + addDummyTransactions(responseBody) + Log.d("Response", "transactionDummyData: $transactionDummyData") + } + + Toast.makeText(this@ScanActivity, "Image uploaded successfully", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this@ScanActivity, "Server error: ${response.code}", Toast.LENGTH_SHORT).show() } @@ -229,26 +284,10 @@ class ScanActivity : AppCompatActivity() { }) } else { - Toast.makeText(this, "Gagal membaca gambar", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "Failed to read image", Toast.LENGTH_SHORT).show() } } - private fun uriToFile(uri: Uri, context: Context): File { - // Ini harus berhasil untuk Uri yang diberikan oleh FileProvider - Log.d("ScanActivity", "Uri: $uri") - - val tempFile = File.createTempFile("upload_", ".jpg", context.cacheDir) - tempFile.deleteOnExit() - - val inputStream = context.contentResolver.openInputStream(uri) - ?: throw IOException("Unable to open URI"); - - FileOutputStream(tempFile).use { outputStream -> - inputStream.copyTo(outputStream) - } - return tempFile - } - @Throws(IOException::class) private fun createImageFile(): File { val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) @@ -258,6 +297,33 @@ class ScanActivity : AppCompatActivity() { } } + private fun addDummyTransactions(response: String) { + val transactions = parseTransactions(response) + + // Tampilkan data transaksi pada ListView + val listView: ListView = findViewById(R.id.listDummyTransaction) + val adapter = TransactionDummyAdapter(this, transactions) + listView.adapter = adapter + } + + private fun parseTransactions(responseBody: String): MutableList<TransactionDummy> { + val transactions = mutableListOf<TransactionDummy>() + responseBody.let { + val jsonObject = JSONObject(it) + val itemsObject = jsonObject.getJSONObject("items") + val itemsArray = itemsObject.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") + val transaction = TransactionDummy(name, qty, price) + transactions.add(transaction) + } + } + return transactions + } @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/res/layout/activity_scan.xml b/app/src/main/res/layout/activity_scan.xml index 5889fb73bc1a386e37c567aa8122c147654a3c88..31993d2ea7472b119eb840144cb6f3cbb96bc4f3 100644 --- a/app/src/main/res/layout/activity_scan.xml +++ b/app/src/main/res/layout/activity_scan.xml @@ -6,82 +6,95 @@ android:layout_height="match_parent" tools:context=".ScanActivity"> - <TextView - android:id="@+id/statusTitleText" - android:layout_width="316dp" - android:layout_height="74dp" - android:gravity="center_horizontal|center_vertical" - android:text="Scan" - android:textAlignment="center" - android:textAppearance="@style/TextAppearance.AppCompat.Large" - android:textStyle="bold" - app:layout_constraintBottom_toTopOf="@+id/imageView" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.904" - tools:ignore="TextSizeCheck" /> - <Button android:id="@+id/scanBtn" - android:layout_width="wrap_content" + android:layout_width="75dp" android:layout_height="wrap_content" + android:layout_marginStart="8dp" android:text="Scan" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintEnd_toStartOf="@+id/uploadBtn" - app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView" - app:layout_constraintVertical_bias="0.358" /> + app:layout_constraintVertical_bias="0.3" /> <Button - android:id="@+id/uploadBtn" - android:layout_width="wrap_content" + android:id="@+id/galleryBtn" + android:layout_width="90dp" android:layout_height="wrap_content" - android:text="Upload" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" + android:layout_marginStart="8dp" + android:text="Album" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" app:layout_constraintStart_toEndOf="@+id/scanBtn" - app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView" - app:layout_constraintVertical_bias="0.358" /> + app:layout_constraintVertical_bias="0.3"/> <Button - android:id="@+id/sendBtn" - android:layout_width="wrap_content" + android:id="@+id/uploadBtn" + android:layout_width="75dp" android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" android:text="Send" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.88" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintEnd_toStartOf="@+id/saveTransactionsBtn" + app:layout_constraintStart_toEndOf="@+id/galleryBtn" app:layout_constraintTop_toBottomOf="@+id/imageView" - app:layout_constraintVertical_bias="0.358" /> + app:layout_constraintVertical_bias="0.3" /> <ImageView android:id="@+id/imageView" android:layout_width="335dp" android:layout_height="335dp" - app:layout_constraintBottom_toBottomOf="parent" + android:contentDescription="PreviewImg" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.393" - tools:srcCompat="@tools:sample/avatars" - android:contentDescription="PreviewImg" /> + app:layout_constraintVertical_bias="0.366" + tools:srcCompat="@tools:sample/avatars" /> <Button android:id="@+id/backBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Back" - app:layout_constraintBottom_toTopOf="@+id/statusTitleText" + app:layout_constraintBottom_toTopOf="@+id/imageView" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.151" + app:layout_constraintHorizontal_bias="0.117" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="1.0" /> + app:layout_constraintVertical_bias="0.794" /> + + <Button + android:id="@+id/saveTransactionsBtn" + android:layout_width="80dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:text="Save" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imageView" + app:layout_constraintVertical_bias="0.3" /> + + <ListView + android:id="@+id/listDummyTransaction" + android:layout_width="324dp" + android:layout_height="211dp" + android:layout_marginBottom="16dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.494" + app:layout_constraintStart_toStartOf="parent" /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Transactions:" + android:textSize="25sp" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.165" + app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_confirm_transaction.xml b/app/src/main/res/layout/dialog_confirm_transaction.xml new file mode 100644 index 0000000000000000000000000000000000000000..7cfa130428c6d1c2ba8bee37b995d85d8827269e --- /dev/null +++ b/app/src/main/res/layout/dialog_confirm_transaction.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <TextView + android:id="@+id/tvConfirmMessage" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + android:text="Would you like to confirm and save these transaction(s)" + android:textSize="16sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="end"> + + <Button + android:id="@+id/btnCancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Cancel" + android:layout_marginEnd="8dp"/> + + <Button + android:id="@+id/btnOkay" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Okay"/> + </LinearLayout> +</LinearLayout>