diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2563d894d8155def992d4597a7e543de86a5e9b8..22bbc618a84d17e0e52b0e4a2362051654e9107e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,10 +56,6 @@ > </activity> - <activity android:name=".ScanActivity" - android:exported="false" - > - </activity> <meta-data android:name="preloaded_fonts" @@ -75,6 +71,6 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> - + <service android:name=".backgroundService.JWTValidationService"/> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/com/example/pbd_jwr/LoginActivity.kt b/app/src/main/java/com/example/pbd_jwr/LoginActivity.kt index 238340df6670d7b737518fe7c570958537cd6867..bb2613c43a996c4177f94008b4d6a0d5e9d8e579 100644 --- a/app/src/main/java/com/example/pbd_jwr/LoginActivity.kt +++ b/app/src/main/java/com/example/pbd_jwr/LoginActivity.kt @@ -49,7 +49,6 @@ class LoginActivity : AppCompatActivity() { connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager networkCallback = NetworkCallbackImplementation(this) - registerNetworkCallback() if (isLoggedIn()) { // If the user credential is stored, login and start the MainActivity @@ -113,7 +112,6 @@ class LoginActivity : AppCompatActivity() { override fun onDestroy() { super.onDestroy() - unregisterNetworkCallback() } private fun isLoggedIn(): Boolean { 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 a3eb223fcefe2ba8127340ad2d4d6c6925e2d20f..3ea5fe8b347aeb67a6422a905296b415a7244b56 100644 --- a/app/src/main/java/com/example/pbd_jwr/MainActivity.kt +++ b/app/src/main/java/com/example/pbd_jwr/MainActivity.kt @@ -83,7 +83,6 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - if (!isLocationPermissionGranted()) { requestLocationPermission() } @@ -98,19 +97,23 @@ class MainActivity : AppCompatActivity() { // menu should be considered as top level destinations. val appBarConfiguration = AppBarConfiguration( setOf( - R.id.navigation_transaction, R.id.navigation_dashboard, R.id.navigation_settings + R.id.navigation_transaction, R.id.navigation_dashboard, R.id.navigation_settings, R.id.navigation_transaction_add, R.id.navigation_transaction_detail, R.id.navigation_twibbon, R.id.navigation_scan ) ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) + val fab: FloatingActionButton = binding.fabScan fab.setOnClickListener { - // Start ScanActivity - val intent = Intent(this, ScanActivity::class.java) - startScanActivityForResult.launch(intent) + // Navigate ScanFragment + navController.navigate(R.id.navigation_scan) + val menuItem = navView.menu.findItem(R.id.navigation_scan) + menuItem.isChecked = true + } navView.setOnItemSelectedListener { menuItem -> when (menuItem.itemId) { + R.id.navigation_transaction -> { // Handle transaction navigation navController.navigate(R.id.navigation_transaction) @@ -136,6 +139,10 @@ class MainActivity : AppCompatActivity() { finish() true } + R.id.navigation_scan->{ + navController.navigate(R.id.navigation_scan) + true + } else -> false } } @@ -152,8 +159,9 @@ class MainActivity : AppCompatActivity() { connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager networkCallback = NetworkCallbackImplementation(this) registerNetworkCallback() + val serviceIntent = Intent(this, JWTValidationService::class.java) + startService(serviceIntent) } - private fun isLocationPermissionGranted(): Boolean { return ContextCompat.checkSelfPermission( this, @@ -161,6 +169,7 @@ class MainActivity : AppCompatActivity() { ) == PackageManager.PERMISSION_GRANTED } +// Request location permission private fun requestLocationPermission() { ActivityCompat.requestPermissions( this, @@ -204,10 +213,13 @@ class MainActivity : AppCompatActivity() { override fun onPause() { super.onPause() unregisterNetworkCallback() + val serviceIntent = Intent(this, JWTValidationService::class.java) + stopService(serviceIntent) } override fun onDestroy() { super.onDestroy() + val serviceIntent = Intent(this, JWTValidationService::class.java) stopService(serviceIntent) LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) diff --git a/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt b/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt deleted file mode 100644 index b3718c8c7cdbbdb6ef549c873287946fd791b351..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/pbd_jwr/ScanActivity.kt +++ /dev/null @@ -1,368 +0,0 @@ -package com.example.pbd_jwr - -import android.content.Intent -import android.graphics.Bitmap -import android.os.Bundle -import android.provider.MediaStore -import android.widget.Button -import android.widget.ImageView -import androidx.appcompat.app.AppCompatActivity -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.widget.Toast -import okhttp3.* -import okhttp3.MediaType.Companion.toMediaTypeOrNull -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.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - - -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 - const val REQUEST_CAMERA_PERMISSION = 2 - const val PICK_IMAGE = 3 - 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) - setContentView(R.layout.activity_scan) - - 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 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() - } - - scanButton.setOnClickListener { - checkAndRequestPermissions() - } - - galleryBtn.setOnClickListener { - openGallery() - } - - uploadButton.setOnClickListener { - if (imageUri != null) { - uploadImage(imageUri!!) - } else { - Toast.makeText(this, "No image selected or taken", Toast.LENGTH_SHORT).show() - } - } - - - saveBtn.setOnClickListener { - handleSave() - } - } - - private fun checkAndRequestPermissions() { - val permissionsNeeded = mutableListOf<String>() - - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - permissionsNeeded.add(Manifest.permission.CAMERA) - } - - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - permissionsNeeded.add(Manifest.permission.READ_EXTERNAL_STORAGE) - } - - if (permissionsNeeded.isNotEmpty()) { - ActivityCompat.requestPermissions(this, permissionsNeeded.toTypedArray(), REQUEST_STORAGE_PERMISSION) - } else { - // Semua izin sudah diberikan - openCamera() - } - } - - @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) - } else { - Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> - takePictureIntent.resolveActivity(packageManager)?.also { - - val photoFile: File? = try { - createImageFile() - } catch (ex: IOException) { - // Error occurred while creating the File - null - } - - photoFile?.also { - val photoURI: Uri = FileProvider.getUriForFile( - this, - "JWR.provider", - it - ) - Log.d("ScanActivity", "Uri: $photoURI") - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) - imageUri = photoURI // Simpan Uri untuk digunakan nanti - startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) - } - } - } - } - } - - private fun startCameraIntent() { - Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> - takePictureIntent.resolveActivity(packageManager)?.also { - startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) - } - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - when (requestCode) { - REQUEST_STORAGE_PERMISSION -> { - if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { - openCamera() - } else { - 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, "Camera permission is required", Toast.LENGTH_SHORT).show() - } - } - } - } - - private fun openGallery() { - val intent = Intent(Intent.ACTION_PICK) - intent.type = "image/*" - 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") - - FileOutputStream(compressedFile).use { outStream -> - // Kompres bitmap ke format JPEG dengan kualitas 75% (sesuaikan sesuai kebutuhan) - bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outStream) - outStream.flush() - } - - return compressedFile - } - - @SuppressLint("Recycle") - private fun uploadImage(imageUri: Uri) { - val token = sharedPreferences.getString("token", null) - if (token.isNullOrEmpty()) { - runOnUiThread { - Toast.makeText(this, "Token not found.", Toast.LENGTH_SHORT).show() - } - return - } - - val bitmap = getBitmapFromUri(imageUri, this) - if (bitmap != null) { - // Kompres dan simpan bitmap ke file baru - val compressedFile = compressAndSaveBitmap(bitmap, this) - - val requestBody = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("file", compressedFile.name, compressedFile.asRequestBody("image/jpeg".toMediaTypeOrNull())) - .build() - - val request = Request.Builder() - .url("https://pbd-backend-2024.vercel.app/api/bill/upload") - .addHeader("Authorization", "Bearer $token") - .post(requestBody) - .build() - - OkHttpClient().newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - runOnUiThread { Toast.makeText(this@ScanActivity, "Failed to upload image", Toast.LENGTH_SHORT).show() } - } - - override fun onResponse(call: Call, response: Response) { - runOnUiThread { - if (response.isSuccessful) { - 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() - } - } - } - }) - - } else { - Toast.makeText(this, "Failed to read image", Toast.LENGTH_SHORT).show() - } - } - - @Throws(IOException::class) - private fun createImageFile(): File { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)!! - return File.createTempFile("JPEG_${timeStamp}_", ".jpg", storageDir).apply { - currentPhotoPath = absolutePath - } - } - - 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 - } - - private fun deleteImageFile() { - imageUri?.let { uri -> - try { - val contentResolver = applicationContext.contentResolver - val deleteSuccess = contentResolver.delete(uri, null, null) > 0 - if (deleteSuccess) { - Log.d("ScanActivity", "Image deleted successfully.") - } else { - Log.d("ScanActivity", "Failed to delete image.") - } - } catch (e: Exception) { - Log.e("ScanActivity", "Error deleting image", e) - } - } - } - - - override fun finish() { - deleteImageFile() - super.finish() - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { - val imageUri = Uri.parse(currentPhotoPath) - imageUri?.let { - findViewById<ImageView>(R.id.imageView).setImageURI(it) - } - } else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK) { - data?.data?.let { uri -> - findViewById<ImageView>(R.id.imageView).setImageURI(uri) - this.imageUri = uri // Simpan Uri dari gambar yang dipilih - } - } - } -} - diff --git a/app/src/main/java/com/example/pbd_jwr/backgroundService/JWTValidationService.kt b/app/src/main/java/com/example/pbd_jwr/backgroundService/JWTValidationService.kt index d323d3a5a9a30cf690f8948e65e0572903437f5d..2a10b2ff00663d7e68d479f660fd42dd33a4bb0e 100644 --- a/app/src/main/java/com/example/pbd_jwr/backgroundService/JWTValidationService.kt +++ b/app/src/main/java/com/example/pbd_jwr/backgroundService/JWTValidationService.kt @@ -28,7 +28,7 @@ import kotlin.math.log class JWTValidationService : Service() { private lateinit var handler: Handler private lateinit var runnable: Runnable - private val INTERVAL: Long = 5 * 60 * 1000 // 5 minutes in milliseconds + private val INTERVAL: Long = 2 * 60 * 1000 // 1 minutes in milliseconds private var client = OkHttpClient() private val validateTokenURL = "https://pbd-backend-2024.vercel.app/api/auth/token" @@ -76,7 +76,7 @@ class JWTValidationService : Service() { override fun onFailure(call: Call, e: IOException) { handler.post{ - Toast.makeText(applicationContext, "Failed to re-login", Toast.LENGTH_SHORT).show() + Toast.makeText(applicationContext, "Connection error", Toast.LENGTH_SHORT).show() } @@ -97,21 +97,23 @@ class JWTValidationService : Service() { val password = sharedPreferences.getString("password","") var isRememberTemp = sharedPreferences.getString("isRemember","") var isRemember = false - if (isRememberTemp == "true"){ - isRemember = true - }else{ - isRemember = false - } + isRemember = isRememberTemp == "true" it.close() if (email != null && password != null) { post(email,password,isRemember) + handler.post{ + Toast.makeText(applicationContext,"Re-login success", Toast.LENGTH_SHORT).show() + } + } else { + handler.post{ + Toast.makeText(applicationContext, "Server Error", Toast.LENGTH_SHORT).show() + } } }else{ handler.post{ Toast.makeText(applicationContext,"Token is not exp", Toast.LENGTH_SHORT).show() } - return } } ?: run { @@ -119,7 +121,7 @@ class JWTValidationService : Service() { }else{ handler.post{ - Toast.makeText(applicationContext,"Failed to check token, server error", Toast.LENGTH_SHORT).show() + Toast.makeText(applicationContext,"Failed to check token, Server error", Toast.LENGTH_SHORT).show() } } @@ -128,7 +130,6 @@ class JWTValidationService : Service() { } }) - } fun isTokenExpired(expirationTime: Long): Boolean { val currentTimeSeconds = Instant.now().epochSecond diff --git a/app/src/main/java/com/example/pbd_jwr/ui/scan/ScanFragment.kt b/app/src/main/java/com/example/pbd_jwr/ui/scan/ScanFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..8bc6e6e52693832c9ab2dc835c2bbdfb5fffb122 --- /dev/null +++ b/app/src/main/java/com/example/pbd_jwr/ui/scan/ScanFragment.kt @@ -0,0 +1,348 @@ +package com.example.pbd_jwr.ui.scan + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity.RESULT_OK +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.provider.MediaStore +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.ListView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.example.pbd_jwr.R +import com.example.pbd_jwr.data.entity.Transaction +import com.example.pbd_jwr.data.model.Category +//import com.example.pbd_jwr.ScanActivity +import com.example.pbd_jwr.databinding.FragmentScanBinding +import com.example.pbd_jwr.encryptedSharedPref.EncryptedSharedPref +import com.example.pbd_jwr.ui.transaction.TransactionDummyAdapter +import com.example.pbd_jwr.ui.transaction.TransactionViewModel +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.asRequestBody +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.roundToInt + +class ScanFragment : Fragment() { + + private var imageUri: Uri? = null + private var currentPhotoPath: String = "" + private lateinit var sharedPreferences: SharedPreferences + var transactionDummyData: String = "" + private var _binding: FragmentScanBinding? = null + private lateinit var mTransactionViewModel: TransactionViewModel + + + private val binding get() = _binding!! + + companion object { + const val REQUEST_CAMERA_PERMISSION = 101 + const val REQUEST_STORAGE_PERMISSION = 102 + const val REQUEST_IMAGE_CAPTURE = 1 + const val PICK_IMAGE = 3 + } + + data class TransactionDummy( + val name: String, + val qty: Int, + val price: Double + ) + + private val startCamera = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + val imageUri = Uri.parse(currentPhotoPath) + binding.imageView.setImageURI(imageUri) + binding.uploadBtn.isEnabled = true + } + } + + private val pickImage = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> + uri?.let { + binding.imageView.setImageURI(it) + imageUri = it + binding.uploadBtn.isEnabled = true + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = FragmentScanBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.uploadBtn.isEnabled = false + binding.saveTransactionsBtn.isEnabled = false + + mTransactionViewModel = ViewModelProvider(this)[TransactionViewModel::class.java] + sharedPreferences = EncryptedSharedPref.create(requireContext(), "login") + resetTransaction() + + binding.scanBtn.setOnClickListener { + checkAndRequestPermissions(Manifest.permission.CAMERA, REQUEST_CAMERA_PERMISSION) { + openCamera() + } + } + + binding.galleryBtn.setOnClickListener { + pickImage.launch("image/*") + } + + binding.uploadBtn.setOnClickListener { + imageUri?.let { uri -> + uploadImage(uri) + } ?: run { + Toast.makeText(context, "No image selected or taken", Toast.LENGTH_SHORT).show() + } + } + + binding.saveTransactionsBtn.setOnClickListener { + handleSave() + } + } + + private fun checkAndRequestPermissions(permission: String, requestCode: Int, grantedAction: () -> Unit) { + when { + ContextCompat.checkSelfPermission(requireContext(), permission) != PackageManager.PERMISSION_GRANTED -> { + requestPermissions(arrayOf(permission), requestCode) + } + else -> grantedAction() + } + } + + @Deprecated("Deprecated in Java") + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + when (requestCode) { + REQUEST_CAMERA_PERMISSION -> openCamera() + // Handle other permissions if necessary + } + } else { + Toast.makeText(context, "Permission denied", Toast.LENGTH_SHORT).show() + } + } + + private fun openCamera() { + val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + activity?.packageManager?.let { packageManager -> + takePictureIntent.resolveActivity(packageManager)?.also { + val photoFile: File? = try { + createImageFile() + } catch (ex: IOException) { + // Error occurred while creating the File + null + } + photoFile?.also { + val photoURI: Uri = FileProvider.getUriForFile(requireContext(), "JWR.provider", it) + startCamera.launch(Intent(takePictureIntent).apply { + putExtra(MediaStore.EXTRA_OUTPUT, photoURI) + }) + imageUri = photoURI + } + } + } + } + + private fun openGallery() { + val intent = Intent(Intent.ACTION_PICK) + intent.type = "image/*" + startActivityForResult(intent, PICK_IMAGE) + } + + private fun uploadImage(imageUri: Uri) { + val token = sharedPreferences.getString("token", null) + if (token.isNullOrEmpty()) { + activity?.runOnUiThread { + Toast.makeText(requireContext(), "Token not found.", Toast.LENGTH_SHORT).show() + } + return + } + + val bitmap = getBitmapFromUri(imageUri, requireContext()) + if (bitmap != null) { + val compressedFile = compressAndSaveBitmap(bitmap, requireContext()) + + // Setup request with OkHttpClient as before + val requestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", compressedFile.name, compressedFile.asRequestBody("image/jpeg".toMediaTypeOrNull())) + .build() + + val request = Request.Builder() + .url("https://pbd-backend-2024.vercel.app/api/bill/upload") + .addHeader("Authorization", "Bearer $token") + .post(requestBody) + .build() + + OkHttpClient().newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + activity?.runOnUiThread { + Toast.makeText(requireContext(), "Failed to upload image", Toast.LENGTH_SHORT).show() + } + } + + override fun onResponse(call: Call, response: Response) { + activity?.runOnUiThread { + if (response.isSuccessful) { + val responseBody = response.body?.string() +// + Log.d("Response", "response body: $responseBody") + + if (responseBody != null) { + transactionDummyData = responseBody + addDummyTransactions(transactionDummyData) + Log.d("Response", "transactionDummyData: $transactionDummyData") + } + + binding.saveTransactionsBtn.isEnabled = true + binding.uploadBtn.isEnabled = false + + Toast.makeText(requireContext(), "Image uploaded successfully", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(requireContext(), "Server error: ${response.code}", Toast.LENGTH_SHORT).show() + } + } + } + }) + } else { + Toast.makeText(requireContext(), "Failed to read image", Toast.LENGTH_SHORT).show() + } + } + + @SuppressLint("InflateParams") + private fun handleSave() { + if (transactionDummyData.isEmpty()) { + Toast.makeText(context, "No transactions read", Toast.LENGTH_SHORT).show() + } else { + // Inflate the dialog layout + val dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_confirm_transaction, null) + val dialogBuilder = AlertDialog.Builder(requireContext()).apply { + setView(dialogView) + setCancelable(true) // Allows dialog to be canceled with back button or by tapping outside + } + + val dialog = dialogBuilder.create() + + dialogView.findViewById<Button>(R.id.btnCancel).setOnClickListener { + dialog.dismiss() + } + + dialogView.findViewById<Button>(R.id.btnOkay).setOnClickListener { + + 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 = Category.EXPENSE + val price = itemObject.getDouble("price") + val qty = itemObject.getInt("qty") + val amount = (qty * price * 1000).roundToInt() / 1000.0 + val latitude = 6.8915 + val longitude = 107.6107 + val currentUserEmail = sharedPreferences.getString("email", "") ?: "" + val date = Date().time + + mTransactionViewModel.addTransaction(Transaction(email = currentUserEmail, title = name, category = category, amount = amount, latitude = latitude, longitude = longitude, date = date)) + } + dialog.dismiss() + resetTransaction() + Toast.makeText(context, "Transactions saved", Toast.LENGTH_SHORT).show() + } + + dialog.show() + } + } + + private fun resetTransaction() { + binding.uploadBtn.isEnabled = false + binding.saveTransactionsBtn.isEnabled = false + + binding.imageView.setImageResource(R.drawable.baseline_insert_photo_24) + addDummyTransactions("{\"items\":{\"items\":[{\"name\":\"none\",\"qty\":0,\"price\":0}]}}") + } + + + 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 { + val compressedFile = File(context.externalCacheDir, "compressed_${System.currentTimeMillis()}.jpg") + FileOutputStream(compressedFile).use { outStream -> + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outStream) + outStream.flush() + } + return compressedFile + } + @Throws(IOException::class) + private fun createImageFile(): File { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File? = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File.createTempFile( + "JPEG_${timeStamp}_", /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ).also { + currentPhotoPath = it.absolutePath + } + } + + + private fun addDummyTransactions(response: String) { + val transactions = parseTransactions(response) + + // Pastikan view sudah dibuat dan context tersedia sebelum mengakses binding atau requireContext() + val adapter = TransactionDummyAdapter(requireContext(), transactions) + + // Gunakan binding untuk mengakses ListView dan menetapkan adapter + binding.listDummyTransaction.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 + } + +} diff --git a/app/src/main/java/com/example/pbd_jwr/ui/scan/ScanViewModel.kt b/app/src/main/java/com/example/pbd_jwr/ui/scan/ScanViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..953040d873de54d9a54ae31cef5e5d1a321550e8 --- /dev/null +++ b/app/src/main/java/com/example/pbd_jwr/ui/scan/ScanViewModel.kt @@ -0,0 +1,13 @@ +package com.example.pbd_jwr.ui.scan + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class ScanViewModel : ViewModel() { + + private val _text = MutableLiveData<String>().apply { + value = "This is scan Fragment" + } + val text: LiveData<String> = _text +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDetailFragment.kt b/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDetailFragment.kt index 1b2932d412dc1d5708d35e4aa8a1d680b8dbb87b..9f6696a15b62aa4673a5a248e1eac7a194d61026 100644 --- a/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDetailFragment.kt +++ b/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDetailFragment.kt @@ -1,6 +1,5 @@ package com.example.pbd_jwr.ui.transaction -import android.app.AlertDialog import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -51,19 +50,15 @@ class TransactionDetailFragment : Fragment(), OnMapReadyCallback { if (transaction != null) { displayTransactionDetails(transaction) - binding.btnBack.setOnClickListener { - findNavController().popBackStack() - } - binding.btnDelete.setOnClickListener { - showDeleteConfirmationDialog(transaction) + transaction?.let { deleteTransaction(it) } } binding.btnEdit.setOnClickListener { val bundle = Bundle().apply { putParcelable("transaction", transaction) - putLong("transactionId", transaction.id) - putBoolean("editMode", true) + putLong("transactionId", transaction.id) // Pass the transaction ID + putBoolean("editMode", true) // Set edit mode to true } findNavController().navigate(R.id.action_transactionDetailFragment_to_transactionAddFragment, bundle) } diff --git a/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDummyAdapter.kt b/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDummyAdapter.kt index 7de77e9eb4d4fd0e397a3c786628fe8ce9dd85c7..529ccd7ef8017a0bba59cbf2a1b75e2e2b3d465d 100644 --- a/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDummyAdapter.kt +++ b/app/src/main/java/com/example/pbd_jwr/ui/transaction/TransactionDummyAdapter.kt @@ -7,11 +7,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter -import com.example.pbd_jwr.ScanActivity +import com.example.pbd_jwr.ui.scan.ScanFragment import com.example.pbd_jwr.databinding.TransactionDummyDetailBinding -class TransactionDummyAdapter(context: Context, transactions: List<ScanActivity.TransactionDummy>) : - ArrayAdapter<ScanActivity.TransactionDummy>(context, 0, transactions) { +class TransactionDummyAdapter(context: Context, transactions: List<ScanFragment.TransactionDummy>) : + ArrayAdapter<ScanFragment.TransactionDummy>(context, 0, transactions) { @SuppressLint("SetTextI18n") override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { diff --git a/app/src/main/res/drawable/baseline_edit_square_24.xml b/app/src/main/res/drawable/baseline_edit_square_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..d084bf24e95f3514e2fd9629dc16ccbe5ff58de0 --- /dev/null +++ b/app/src/main/res/drawable/baseline_edit_square_24.xml @@ -0,0 +1,7 @@ +<vector android:height="24dp" android:tint="#B29F7A" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M7,17V9.93L13.93,3l0,0H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-8.93L14.07,17H7z"/> + <path android:fillColor="@android:color/white" android:pathData="M9,15l4.24,0l7.2,-7.2l-4.24,-4.24l-7.2,7.2z"/> + <path android:fillColor="@android:color/white" android:pathData="M22.91,2.49l-1.41,-1.41c-0.78,-0.78 -2.05,-0.78 -2.83,0l-1.06,1.06l4.24,4.24l1.06,-1.06C23.7,4.54 23.7,3.27 22.91,2.49z"/> +</vector> diff --git a/app/src/main/res/drawable/baseline_file_download_24.xml b/app/src/main/res/drawable/baseline_file_download_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..a6371e8a94e09d1c3b0c48b6cfc50c4af78a5668 --- /dev/null +++ b/app/src/main/res/drawable/baseline_file_download_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#B29F7A" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/> +</vector> diff --git a/app/src/main/res/drawable/baseline_random_24.xml b/app/src/main/res/drawable/baseline_random_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..7e32cfc54b992e8c10b3656320e452905b528df3 --- /dev/null +++ b/app/src/main/res/drawable/baseline_random_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#B29F7A" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M15.5,13.5c0,2 -2.5,3.5 -2.5,5h-2c0,-1.5 -2.5,-3 -2.5,-5c0,-1.93 1.57,-3.5 3.5,-3.5h0C13.93,10 15.5,11.57 15.5,13.5zM13,19.5h-2V21h2V19.5zM19,13c0,1.68 -0.59,3.21 -1.58,4.42l1.42,1.42C20.18,17.27 21,15.23 21,13c0,-2.74 -1.23,-5.19 -3.16,-6.84l-1.42,1.42C17.99,8.86 19,10.82 19,13zM16,5l-4,-4v3c0,0 0,0 0,0c-4.97,0 -9,4.03 -9,9c0,2.23 0.82,4.27 2.16,5.84l1.42,-1.42C5.59,16.21 5,14.68 5,13c0,-3.86 3.14,-7 7,-7c0,0 0,0 0,0v3L16,5z"/> +</vector> diff --git a/app/src/main/res/drawable/baseline_twibbon_24.xml b/app/src/main/res/drawable/baseline_twibbon_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..699af52466ce2c9226f33f59e3f57f8b7d59c229 --- /dev/null +++ b/app/src/main/res/drawable/baseline_twibbon_24.xml @@ -0,0 +1,6 @@ +<vector android:height="24dp" android:tint="#B29F7A" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M9,3L7.17,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2h-3.17L15,3L9,3zM12,18c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/> + <path android:fillColor="@android:color/white" android:pathData="M12,17l1.25,-2.75L16,13l-2.75,-1.25L12,9l-1.25,2.75L8,13l2.75,1.25z"/> +</vector> diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 700bd6ed75b2ecab1eb3df65ab9fe6894d83ef2f..c1fbd4afc715bd4c3d8eedb60caa28cd3cb25ced 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,11 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/container" xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:context=".MainActivity" + app:layout_constraintTop_toTopOf="parent" + android:overScrollMode="never" + > +<androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/container" tools:context=".LoginActivity" + android:paddingBottom="30dp" > <TextView @@ -85,6 +93,7 @@ android:layout_width="150dp" android:layout_height="60dp" app:layout_constraintTop_toBottomOf="@id/rememberMe" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" android:theme="@style/Theme.login" @@ -93,4 +102,6 @@ android:fontFamily="@font/press_start_2p" android:paddingTop="12dp" /> -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file +</androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0a62c782699a00bebe25f55ed10e1915d2ecd708..ab8fb907c88472c9c3c502384f9520a23f144af6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,6 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingTop="10dp" tools:context=".MainActivity" > @@ -28,7 +27,6 @@ android:layout_height="match_parent" app:menu="@menu/bottom_nav_menu" android:layout_marginEnd="20dp" - android:background="@android:color/transparent" app:itemTextAppearanceActive="@style/BottomNavigationView.Active" app:itemTextAppearanceInactive="@style/BottomNavigationView" /> @@ -57,7 +55,9 @@ app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.0" - app:navGraph="@navigation/mobile_navigation" /> + app:navGraph="@navigation/mobile_navigation" + android:layout_marginBottom="60dp" + /> diff --git a/app/src/main/res/layout/activity_scan.xml b/app/src/main/res/layout/activity_scan.xml deleted file mode 100644 index 31993d2ea7472b119eb840144cb6f3cbb96bc4f3..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/activity_scan.xml +++ /dev/null @@ -1,100 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".ScanActivity"> - - <Button - android:id="@+id/scanBtn" - android:layout_width="75dp" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:text="Scan" - app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" - app:layout_constraintVertical_bias="0.3" /> - - <Button - android:id="@+id/galleryBtn" - android:layout_width="90dp" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:text="Album" - app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" - app:layout_constraintStart_toEndOf="@+id/scanBtn" - app:layout_constraintTop_toBottomOf="@+id/imageView" - app:layout_constraintVertical_bias="0.3"/> - - <Button - 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_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.3" /> - - <ImageView - android:id="@+id/imageView" - android:layout_width="335dp" - android:layout_height="335dp" - 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.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/imageView" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.117" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - 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/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index feed448c4aac31ab15bf7ced37bf9e323ab9f82f..da3f3848bfa38319a907afe68514c5e7a2458748 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -1,13 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".ui.dashboard.DashboardFragment"> + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + android:overScrollMode="never" + > +<RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:context=".ui.dashboard.DashboardFragment" + android:paddingBottom="40dp" + > <com.github.mikephil.charting.charts.PieChart android:id="@+id/pieChart" android:layout_width="match_parent" - android:layout_height="600dp" /> + android:layout_height="400dp" + /> </RelativeLayout> +</ScrollView> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml new file mode 100644 index 0000000000000000000000000000000000000000..89feaea6c2e0ddd15761e2a96667755ef3080b22 --- /dev/null +++ b/app/src/main/res/layout/fragment_scan.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:overScrollMode="never" + > +<androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:context=".ui.scan.ScanFragment" + android:paddingHorizontal="8dp" + > + + <Button + android:id="@+id/scanBtn" + android:layout_width="75dp" + android:layout_height="wrap_content" + android:text="@string/title_scan" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toLeftOf="@id/galleryBtn" + app:layout_constraintTop_toBottomOf="@+id/imageView" + /> + + <Button + android:id="@+id/galleryBtn" + android:layout_width="90dp" + android:layout_height="wrap_content" + android:text="@string/album" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintLeft_toRightOf="@id/scanBtn" + app:layout_constraintRight_toLeftOf="@id/uploadBtn" + app:layout_constraintTop_toBottomOf="@+id/imageView" + /> + + <Button + android:id="@+id/uploadBtn" + android:layout_width="75dp" + android:layout_height="wrap_content" + android:text="@string/send" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintRight_toLeftOf="@id/saveTransactionsBtn" + app:layout_constraintLeft_toRightOf="@id/galleryBtn" + app:layout_constraintTop_toBottomOf="@+id/imageView" + + /> + + <ImageView + android:id="@+id/imageView" + android:layout_width="335dp" + android:layout_height="335dp" + android:contentDescription="@string/previewimg" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:srcCompat="@tools:sample/avatars" /> + + + <Button + android:id="@+id/saveTransactionsBtn" + android:layout_width="80dp" + android:layout_height="wrap_content" + android:text="@string/save" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintLeft_toRightOf="@id/uploadBtn" + app:layout_constraintTop_toBottomOf="@+id/imageView" + /> + + <ListView + android:id="@+id/listDummyTransaction" + android:layout_width="323dp" + android:layout_height="193dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textView" + android:textSize="16sp" + /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/title_transaction" + android:textSize="25sp" + android:layout_marginTop="10dp" + android:layout_marginBottom="10dp" + app:layout_constraintBottom_toTopOf="@+id/listDummyTransaction" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/scanBtn" /> + +</androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index eb8b3c50d7902384666d3b95f4913491037f0703..3f70d183cb7725813857532a8a93ec6c876223fb 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,11 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + android:overScrollMode="never" + > + +<androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" tools:context=".ui.settings.SettingsFragment" - android:paddingBottom="50dp" + android:paddingBottom="20dp" > @@ -13,9 +20,9 @@ android:id="@+id/sendEmailButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="24dp" + android:layout_marginTop="16dp" android:drawableEnd="@drawable/baseline_email_24" - android:paddingHorizontal="50dp" + android:paddingHorizontal="20dp" android:text="@string/send_email" app:layout_constraintBottom_toTopOf="@+id/saveBtn" app:layout_constraintLeft_toLeftOf="parent" @@ -27,36 +34,34 @@ android:id="@+id/saveBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Save" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.498" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.221" /> + android:text="@string/save" + app:layout_constraintTop_toBottomOf="@id/sendEmailButton" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:drawableEnd="@drawable/baseline_file_download_24" + /> <Button android:id="@+id/twibbonButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="26dp" - android:text="Twibbon" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.498" - app:layout_constraintStart_toStartOf="parent" + android:text="@string/twibbon" app:layout_constraintTop_toBottomOf="@+id/saveBtn" - app:layout_constraintVertical_bias="0.024"/> + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:drawableEnd="@drawable/baseline_twibbon_24" + /> <Button android:id="@+id/randomTransactionBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="RandomizeTransaction" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.497" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.233" /> -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file + android:text="@string/randomizetransaction" + app:layout_constraintTop_toBottomOf="@id/twibbonButton" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:drawableEnd="@drawable/baseline_random_24" + /> +</androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml index b68e6ad953576a77ef7786529a70fa20eb214c39..9405331fbf0c9a050c46b154188e66715f3c8ce3 100644 --- a/app/src/main/res/layout/fragment_transaction.xml +++ b/app/src/main/res/layout/fragment_transaction.xml @@ -4,9 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity" - android:paddingBottom="50dp" - + tools:context=".ui.transaction.TransactionFragment" > <View android:id="@+id/view" @@ -25,7 +23,9 @@ app:layout_constraintTop_toBottomOf="@id/view" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"/> + app:layout_constraintEnd_toEndOf="parent" + android:overScrollMode="never" + /> <ImageButton android:id="@+id/btnAdd" diff --git a/app/src/main/res/layout/fragment_transaction_add.xml b/app/src/main/res/layout/fragment_transaction_add.xml index 9acbf35ff3b7fdaf04a55e11415b3363648bdb43..80535a08a7d33ab80ab57aa7c82b80f963bd92c6 100644 --- a/app/src/main/res/layout/fragment_transaction_add.xml +++ b/app/src/main/res/layout/fragment_transaction_add.xml @@ -1,38 +1,48 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="16dp" - tools:context=".MainActivity" - tools:layout_editor_absoluteX="0dp" - tools:layout_editor_absoluteY="22dp"> + android:layout_height="wrap_content" + android:overScrollMode="never" + > - <!-- Back Button --> - <Button - android:id="@+id/backButton" +<androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + tools:context="..ui.transaction.TransactionAddFragment" + android:paddingBottom="30dp" + > + + + <TextView + android:id="@+id/titleAddTransaction" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="16dp" - android:background="?attr/selectableItemBackgroundBorderless" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - + android:text="Title : " + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/editTextTitle" + android:textSize="16sp" + android:paddingHorizontal="2dp" + android:paddingVertical="5dp" + /> <!-- Form Fields --> <EditText android:id="@+id/editTextTitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="36dp" - android:layout_marginEnd="16dp" - android:hint="Title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/backButton" /> + app:layout_constraintTop_toBottomOf="@id/titleAddTransaction" + android:background="@drawable/custom_input" + android:textColor="@color/black" + android:paddingVertical="12dp" + android:paddingHorizontal="8dp" + + /> <!-- Location Field --> @@ -40,52 +50,105 @@ android:id="@+id/spinnerCategory" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="32dp" - android:layout_marginEnd="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/editTextTitle" /> + app:layout_goneMarginTop="5dp" + app:layout_constraintTop_toBottomOf="@id/editTextTitle" + android:textColor="@color/black" + android:paddingVertical="12dp" + android:paddingHorizontal="8dp" + app:layout_constraintBottom_toTopOf="@id/titleAmountTransaction" + android:layout_marginTop="10dp" + android:layout_marginBottom="8dp" + /> + + + + <TextView + android:id="@+id/titleAmountTransaction" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Amount :" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@id/spinnerCategory" + app:layout_constraintBottom_toTopOf="@id/editTextAmount" + android:textSize="16sp" + android:paddingHorizontal="2dp" + android:paddingVertical="5dp" + + /> <EditText android:id="@+id/editTextAmount" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="28dp" - android:layout_marginEnd="16dp" - android:hint="Amount" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/spinnerCategory" /> + app:layout_constraintTop_toBottomOf="@id/titleAmountTransaction" + android:textColor="@color/black" + android:paddingVertical="12dp" + android:paddingHorizontal="8dp" + android:background="@drawable/custom_input" + + /> + + <TextView + android:id="@+id/titleLatTransaction" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Lat : " + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@id/editTextAmount" + app:layout_constraintBottom_toTopOf="@id/editTextLatitude" + android:textSize="16sp" + android:paddingHorizontal="2dp" + android:paddingVertical="5dp" + /> <EditText android:id="@+id/editTextLatitude" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="28dp" - android:layout_marginEnd="16dp" - android:hint="Latitude" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/editTextAmount" /> + app:layout_constraintTop_toBottomOf="@id/titleLatTransaction" + app:layout_constraintBottom_toTopOf="@id/titleLongTransaction" + android:textColor="@color/black" + android:paddingVertical="12dp" + android:paddingHorizontal="8dp" + android:background="@drawable/custom_input" + /> + + <TextView + android:id="@+id/titleLongTransaction" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Long : " + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@id/editTextLatitude" + app:layout_constraintBottom_toTopOf="@id/editTextLongitude" + android:textSize="16sp" + android:paddingHorizontal="2dp" + android:paddingVertical="5dp" + /> <EditText android:id="@+id/editTextLongitude" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="20dp" - android:layout_marginEnd="16dp" - android:hint="Longitude" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/editTextLatitude" /> + app:layout_constraintTop_toBottomOf="@id/titleLongTransaction" + app:layout_constraintBottom_toTopOf="@id/btnSubmit" + android:textColor="@color/black" + android:paddingVertical="12dp" + android:paddingHorizontal="8dp" + android:background="@drawable/custom_input" + /> <!-- Submit Button --> @@ -93,12 +156,14 @@ android:id="@+id/btnSubmit" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" - android:layout_marginBottom="212dp" android:text="@string/submit" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@id/editTextLongitude" - app:layout_constraintHorizontal_bias="0.533" - app:layout_constraintStart_toStartOf="@id/editTextLongitude" /> + app:layout_constraintTop_toBottomOf="@id/editTextLongitude" + android:layout_marginTop="5dp" + /> </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_transaction_detail.xml b/app/src/main/res/layout/fragment_transaction_detail.xml index a0273d26eef8e15c209ce6b00d1ff06ad5addfb6..462686089aba591f1b492e8932ca7698fb0eb7d2 100644 --- a/app/src/main/res/layout/fragment_transaction_detail.xml +++ b/app/src/main/res/layout/fragment_transaction_detail.xml @@ -1,10 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="16dp"> + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + android:overScrollMode="never" + > + + +<androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:paddingTop="10dp" + android:paddingBottom="30dp" + > <TextView android:id="@+id/textViewTitle" @@ -125,17 +136,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewLocation" /> - <Button - android:id="@+id/btnBack" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:elevation="2dp" - android:text="Back" - android:textColor="@android:color/black" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:layout_editor_absoluteX="16dp" - tools:layout_editor_absoluteY="16dp" /> <Button android:id="@+id/btnDelete" @@ -143,23 +143,26 @@ android:layout_height="wrap_content" android:elevation="2dp" android:text="Delete" - android:textColor="@android:color/black" - app:layout_constraintStart_toEndOf="@+id/btnBack" - app:layout_constraintTop_toTopOf="@+id/btnBack" - app:layout_constraintBottom_toBottomOf="@+id/btnBack" - android:layout_marginStart="8dp" /> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:layout_marginEnd="5dp" + + /> <Button android:id="@+id/btnEdit" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" android:elevation="2dp" android:text="Edit" - android:textColor="@android:color/black" - app:layout_constraintStart_toEndOf="@+id/btnDelete" - app:layout_constraintTop_toTopOf="@+id/btnDelete" - app:layout_constraintBottom_toBottomOf="@+id/btnDelete" - android:layout_marginStart="8dp" /> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + android:layout_marginStart="5dp" + + android:drawableEnd="@drawable/baseline_edit_square_24" + /> <fragment android:id="@+id/mapFragment" @@ -172,3 +175,5 @@ </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index 434bcfa7121630326f7ccf66f9ee87bfe6d4cb58..026fdbc425c86d47e02723b27fe9aae753d513d0 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -13,7 +13,7 @@ /> <item - android:id="@+id/floatingNav" + android:id="@+id/navigation_scan" android:title="@string/title_scan" /> <item diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 38d3dac50f46d188bed251073775efb8c6956bfd..b9e8c3324b7b853f56c54c6e248ac8744411e821 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -13,28 +13,34 @@ <action android:id="@+id/action_transactionFragment_to_transactionDetailFragment" - app:destination="@id/navigation_transaction_detail" /> + app:destination="@id/navigation_transaction_detail" + android:label="Transaction Detail" + /> <action android:id="@+id/action_transactionFragment_to_transactionAddFragment" - app:destination="@id/navigation_transaction_add" /> + app:destination="@id/navigation_transaction_add" + android:label="Add Transaction" + /> </fragment> <fragment android:id="@+id/navigation_transaction_detail" android:name="com.example.pbd_jwr.ui.transaction.TransactionDetailFragment" - android:label="" + android:label="Detail Transaction" tools:layout="@layout/fragment_transaction_detail"> <action android:id="@+id/action_transactionDetailFragment_to_transactionAddFragment" - app:destination="@id/navigation_transaction_add" /> + app:destination="@id/navigation_transaction_add" + android:label="Add Transaction" + /> </fragment> <fragment android:id="@+id/navigation_transaction_add" android:name="com.example.pbd_jwr.ui.transaction.TransactionAddFragment" - android:label="" + android:label="Add Transaction" tools:layout="@layout/fragment_transaction_add" /> @@ -45,7 +51,11 @@ tools:layout="@layout/fragment_dashboard" /> - + <fragment + android:id="@+id/navigation_scan" + android:name="com.example.pbd_jwr.ui.scan.ScanFragment" + android:label="@string/title_scan" + tools:layout="@layout/fragment_scan" /> <fragment @@ -60,5 +70,5 @@ <fragment android:id="@+id/navigation_twibbon" android:name="com.example.pbd_jwr.ui.twibbon.TwibbonFragment" - android:label="fragment_twibbon" /> + android:label="Twibbon" /> </navigation> \ 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 ac7b33750df4664a157a31eb98f884917b729ce0..74c6cb1e5d9743f090b30ff036d6048d1029db9e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,4 +12,15 @@ <string name="title_scan">Scan</string> <string name="send_email">Send Email</string> <string name="submit">Submit</string> + <string name="save">Save</string> + <string name="randomizetransaction">RandomizeTransaction</string> + <string name="twibbon">Twibbon</string> + <string name="longitude">Longitude</string> + <string name="latitude">Latitude</string> + <string name="amount">Amount</string> + <string name="title">Title</string> + <string name="back">Back</string> + <string name="album">Album</string> + <string name="send">Send</string> + <string name="previewimg">PreviewImg</string> </resources> \ No newline at end of file