diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7714d078ffff2f403f0dcddadce95ec2ec457157..0bb7fd673d7f9a0c7538b391eb73812b5e30efb4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -48,9 +48,9 @@ dependencies { implementation("org.apache.poi:poi:5.2.4") implementation("org.apache.poi:poi-ooxml:5.2.4") implementation("com.google.android.gms:play-services-location:21.2.0") - testImplementation("junit:junit:4.13.2") implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a9ffda85b35ad0056fad0c47b9ba1de951c6788..b75a0b30d2ac9c98ea0e213662aa93828f04aba1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,11 +43,6 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <activity - android:name=".ScanActivity" - android:exported="false" - android:screenOrientation="portrait" /> - <service android:name=".services.JWTExpiry" android:enabled="true" diff --git a/app/src/main/java/com/example/bondoman/MainActivity.kt b/app/src/main/java/com/example/bondoman/MainActivity.kt index 4d9397c6cfac1f168bea3dcbddce6fc265ee0225..5842ec038131f88dd1238a7dedf55e3dba04f173 100644 --- a/app/src/main/java/com/example/bondoman/MainActivity.kt +++ b/app/src/main/java/com/example/bondoman/MainActivity.kt @@ -1,19 +1,29 @@ package com.example.bondoman import android.content.Intent -import androidx.appcompat.app.AppCompatActivity +import android.graphics.Rect import android.os.Bundle +import android.view.MenuItem import android.view.View import android.widget.ImageButton +import androidx.appcompat.app.AppCompatActivity +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.app.NavUtils import androidx.core.content.ContextCompat -import com.example.bondoman.services.JWTExpiry +import androidx.fragment.app.FragmentManager +import androidx.navigation.NavController +import androidx.navigation.Navigation import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.setupActionBarWithNavController +import com.example.bondoman.services.JWTExpiry class MainActivity : AppCompatActivity() { private lateinit var service: Intent - + private lateinit var transactionButton: ImageButton + private lateinit var graphButton: ImageButton + private lateinit var settingButton: ImageButton + private lateinit var scanButton: ImageButton + private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -24,54 +34,79 @@ class MainActivity : AppCompatActivity() { // .commit() val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val toolbarButton = findViewById<ImageButton>(R.id.toolbar_back_button) + navController = navHostFragment.navController + val fragmentManager: FragmentManager = supportFragmentManager - // Get the NavController from the NavHostFragment - val navController = navHostFragment.navController - - val transactionButton = findViewById<ImageButton>(R.id.transaction_button) + transactionButton = findViewById(R.id.transaction_button) transactionButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.navbar_transaction_selector)) transactionButton.isSelected = true - val graphButton = findViewById<ImageButton>(R.id.graph_button) + graphButton = findViewById(R.id.graph_button) graphButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.navbar_graph_selector)) - val settingButton = findViewById<ImageButton>(R.id.setting_button) + settingButton = findViewById(R.id.setting_button) settingButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.navbar_setting_selector)) - val scanButton = findViewById<ImageButton>(R.id.scan_button) + scanButton = findViewById(R.id.scan_button) + toolbarButton.setOnClickListener { + navController.navigateUp() + } transactionButton.setOnClickListener { - transactionButton.isSelected = true - graphButton.isSelected = false - settingButton.isSelected = false - navController.navigate(R.id.transaction_fragment) + navigateTo(R.id.transaction_fragment) } graphButton.setOnClickListener { - transactionButton.isSelected = false - graphButton.isSelected = true - settingButton.isSelected = false - navController.navigate(R.id.graph_fragment) + navigateTo(R.id.graph_fragment) } settingButton.setOnClickListener { - transactionButton.isSelected = false - graphButton.isSelected = false - settingButton.isSelected = true - navController.navigate(R.id.setting_fragment) + navigateTo(R.id.setting_fragment) } scanButton.setOnClickListener { - val intent = Intent(this, ScanActivity::class.java) - startActivity(intent) + navigateTo(R.id.scan_fragment) } + val viewTreeObserver = window.decorView.viewTreeObserver + viewTreeObserver.addOnGlobalLayoutListener { + val r = Rect() + window.decorView.getWindowVisibleDisplayFrame(r) + val screenHeight = window.decorView.rootView.height + val keypadHeight = screenHeight - r.bottom + + if (keypadHeight > screenHeight * 0.15) { + val navbar = findViewById<ConstraintLayout>(R.id.navigation_bar) + navbar.visibility = View.GONE + } else { + val navbar = findViewById<ConstraintLayout>(R.id.navigation_bar) + navbar.visibility = View.VISIBLE + } + } } override fun onSupportNavigateUp(): Boolean { val navController = findNavController(R.id.nav_host_fragment) return navController.navigateUp() || super.onSupportNavigateUp() } + private fun navigateTo(id: Int) { + transactionButton.isSelected = (id == R.id.transaction_fragment) + graphButton.isSelected = (id == R.id.graph_fragment) + settingButton.isSelected = (id == R.id.setting_fragment) + navController.navigate(id) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + // Respond to the action bar's Up/Home button + NavUtils.navigateUpFromSameTask(this) + true + } + else -> super.onOptionsItemSelected(item) + } + } + override fun onDestroy() { super.onDestroy() stopService(service) diff --git a/app/src/main/java/com/example/bondoman/ScanActivity.kt b/app/src/main/java/com/example/bondoman/ScanActivity.kt deleted file mode 100644 index fc87270c684f4fc7bb13cf485115fc410ed7aa93..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/ScanActivity.kt +++ /dev/null @@ -1,259 +0,0 @@ -package com.example.bondoman - -import android.Manifest -import android.content.ContentValues.TAG -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.BitmapFactory -import android.graphics.ImageFormat -import android.graphics.SurfaceTexture -import android.hardware.camera2.CameraCaptureSession -import android.hardware.camera2.CameraDevice -import android.hardware.camera2.CameraManager -import android.hardware.camera2.CaptureRequest -import android.hardware.camera2.TotalCaptureResult -import android.media.ImageReader -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.os.HandlerThread -import android.util.Log -import android.view.Surface -import android.view.TextureView -import android.view.View -import android.widget.ImageButton -import androidx.activity.ComponentActivity -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat - -class ScanActivity : ComponentActivity() { - private var isLivePreview = true - lateinit var capReq: CaptureRequest.Builder - lateinit var handler: Handler - private lateinit var handlerThread: HandlerThread - private lateinit var cameraManager: CameraManager - lateinit var textureView: TextureView - lateinit var cameraCaptureSession: CameraCaptureSession - lateinit var cameraDevice: CameraDevice - // lateinit var captureRequest: CaptureRequest - lateinit var imageReader: ImageReader - - private lateinit var captureButton: ImageButton - private lateinit var recaptureButton: ImageButton - private lateinit var galleryButton: ImageButton - private lateinit var confirmButton: ImageButton - - - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_scan) - captureButton = findViewById(R.id.captureButton) - recaptureButton = findViewById(R.id.recaptureButton) - galleryButton = findViewById(R.id.galleryButton) - confirmButton = findViewById(R.id.confirmButton) - requestPermissionLauncher.launch(CAMERA_PERMISSION) - - } - - override fun onDestroy() { - super.onDestroy() - cameraDevice.close() - handler.removeCallbacksAndMessages(null) - handlerThread.quitSafely() - } - - private fun configureCameraPreview() { - // Create a capture request for preview - - - // Create a new camera capture session for the preview - capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) - val surface = Surface(textureView.surfaceTexture) - capReq.addTarget(surface) - cameraDevice.createCaptureSession(listOf(surface, imageReader.surface), - object : CameraCaptureSession.StateCallback() { - override fun onConfigured(session: CameraCaptureSession) { - cameraCaptureSession = session - cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) - } - - override fun onConfigureFailed(session: CameraCaptureSession) { - - } - - }, handler) - isLivePreview = true - - } - - private fun displayCapturedImage() { - cameraCaptureSession.stopRepeating() - - // Retrieve the captured image data from your ImageReader - val image = imageReader.acquireLatestImage() - val buffer = image.planes[0].buffer - val bytes = ByteArray(buffer.remaining()) - buffer.get(bytes) - val bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) - - // Display the captured image on your TextureView - runOnUiThread { - textureView.surfaceTexture?.let { _ -> - val canvas = textureView.lockCanvas() - if (canvas != null) { - canvas.drawBitmap(bitmapImage, 0f, 0f, null) - textureView.unlockCanvasAndPost(canvas) - } else { - Log.e(TAG, "canvas error") - } - } - } - - // Close and release the Image when done - image.close() - } - - private fun openCamera(){ - - if (ActivityCompat.checkSelfPermission( - this, - Manifest.permission.CAMERA - ) != PackageManager.PERMISSION_GRANTED - ) { - ActivityCompat.requestPermissions( - this, CAMERA_PERMISSION, 0 - ) - return - } - cameraManager.openCamera(cameraManager.cameraIdList[0], object : CameraDevice.StateCallback() { - override fun onOpened(camera: CameraDevice) { - cameraDevice = camera - capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) - val surface = Surface(textureView.surfaceTexture) - capReq.addTarget(surface) - cameraDevice.createCaptureSession(listOf(surface, imageReader.surface), - object : CameraCaptureSession.StateCallback() { - override fun onConfigured(session: CameraCaptureSession) { - cameraCaptureSession = session - cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) - } - - override fun onConfigureFailed(session: CameraCaptureSession) { - - } - - }, handler) - } - - override fun onDisconnected(camera: CameraDevice) { - cameraDevice.close() - } - - override fun onError(camera: CameraDevice, error: Int) { - cameraDevice.close() - Log.e(TAG, "Camera error: $error") - } - - }, handler) - } - - private val requestPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> - permissions.forEach { (_, isGranted) -> - if (isGranted) { - textureView = findViewById(R.id.textureView) - cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager - handlerThread = HandlerThread("videoThread") - handlerThread.start() - handler = Handler(handlerThread.looper) - - textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener { - override fun onSurfaceTextureAvailable( - surface: SurfaceTexture, - width: Int, - height: Int - ) { - openCamera() - } - - override fun onSurfaceTextureSizeChanged( - surface: SurfaceTexture, - width: Int, - height: Int - ) { - } - - override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { - - return false - } - - override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { - } - - } - - imageReader = ImageReader.newInstance(1080, 1323, ImageFormat.JPEG, 1) - - captureButton.apply { - setOnClickListener { - captureButton.visibility = View.INVISIBLE - galleryButton.visibility = View.INVISIBLE - recaptureButton.visibility = View.VISIBLE - confirmButton.visibility = View.VISIBLE - capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) - capReq.addTarget(imageReader.surface) - cameraCaptureSession.capture(capReq.build(), object : CameraCaptureSession.CaptureCallback() { - override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { - super.onCaptureCompleted(session, request, result) - displayCapturedImage() - } - }, null) - - } - } - - recaptureButton.apply { - setOnClickListener { - captureButton.visibility = View.VISIBLE - galleryButton.visibility = View.VISIBLE - recaptureButton.visibility = View.INVISIBLE - confirmButton.visibility = View.INVISIBLE - configureCameraPreview() - } - } - } else { - val intent = Intent(this, MainActivity::class.java) - startActivity(intent) - } - } - } - - - private fun hasRequiredPermissions(): Boolean { - return CAMERA_PERMISSION.all { - ContextCompat.checkSelfPermission( - applicationContext, - it - ) == PackageManager.PERMISSION_GRANTED - } - } - - companion object { - private val CAMERA_PERMISSION = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - arrayOf( - Manifest.permission.CAMERA, - Manifest.permission.READ_MEDIA_IMAGES, - ) - } else { - arrayOf( - Manifest.permission.CAMERA, - Manifest.permission.READ_EXTERNAL_STORAGE, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ScanFragment.kt b/app/src/main/java/com/example/bondoman/ScanFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..a0f7a5741608c50daa0ad79d5a45da47de0720ee --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ScanFragment.kt @@ -0,0 +1,440 @@ +package com.example.bondoman + +import android.Manifest +import android.app.AlertDialog +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.ImageFormat +import android.graphics.SurfaceTexture +import android.hardware.camera2.CameraCaptureSession +import android.hardware.camera2.CameraDevice +import android.hardware.camera2.CameraManager +import android.hardware.camera2.CaptureRequest +import android.hardware.camera2.TotalCaptureResult +import android.hardware.camera2.params.OutputConfiguration +import android.hardware.camera2.params.SessionConfiguration +import android.media.ImageReader +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import android.os.ParcelFileDescriptor +import android.util.Log +import android.view.LayoutInflater +import android.view.Surface +import android.view.TextureView +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.example.bondoman.utils.RetrofitInstance +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody +import retrofit2.HttpException +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.util.concurrent.Executors + + +class ScanFragment : Fragment() { + private lateinit var handler: Handler + private lateinit var handlerThread: HandlerThread + private lateinit var cameraManager: CameraManager + lateinit var capReq: CaptureRequest.Builder + lateinit var textureView: TextureView + lateinit var cameraCaptureSession: CameraCaptureSession + lateinit var imageReader: ImageReader + + private lateinit var captureButton: ImageButton + private lateinit var recaptureButton: ImageButton + private lateinit var galleryButton: ImageButton + private lateinit var confirmButton: ImageButton + + + private lateinit var imageFile: File + + private var job: Job? = null + private var cameraDevice: CameraDevice? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_scan, container, false) + captureButton = view.findViewById(R.id.captureButton) + recaptureButton = view.findViewById(R.id.recaptureButton) + galleryButton = view.findViewById(R.id.galleryButton) + confirmButton = view.findViewById(R.id.confirmButton) + + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + val textView = toolbar.findViewById<TextView>(R.id.toolbar_text) + val transactionButton = requireActivity().findViewById<ImageButton>(R.id.transaction_button) + val graphButton = requireActivity().findViewById<ImageButton>(R.id.graph_button) + val settingButton = requireActivity().findViewById<ImageButton>(R.id.setting_button) + toolbar.setBackgroundColor(Color.parseColor("#1B1A55")) + navbar.setBackgroundResource(R.drawable.navbar_bordered_background) + + textView.text = "Scan Nota" + transactionButton.isSelected = false + graphButton.isSelected = false + settingButton.isSelected = false + + return view + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestPermissionLauncher.launch(CAMERA_PERMISSION) + + } + + override fun onDestroy() { + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + + toolbar.setBackgroundColor( + ContextCompat.getColor( + requireContext(), + android.R.color.transparent + ) + ) + navbar.setBackgroundResource(R.drawable.navbar_background) + super.onDestroy() + cameraDevice?.close() + handler.removeCallbacksAndMessages(null) + handlerThread.quitSafely() + textureView.surfaceTexture?.release() + textureView.surfaceTextureListener = null + + } + + private fun configureCameraPreview() { + capReq = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) + val surface = Surface(textureView.surfaceTexture) + capReq.addTarget(surface) + val outputConfigurationSurface = OutputConfiguration(surface) + val outputConfigurationImageReader = OutputConfiguration(imageReader.surface) + val sessionConfiguration = SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + listOf(outputConfigurationSurface, outputConfigurationImageReader), + Executors.newSingleThreadExecutor(), + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(session: CameraCaptureSession) { + cameraCaptureSession = session + cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) + } + + override fun onConfigureFailed(session: CameraCaptureSession) { + + } + } + ) + + cameraDevice!!.createCaptureSession(sessionConfiguration) + + } + + private fun displayCapturedImage() { + cameraCaptureSession.stopRepeating() + + val image = imageReader.acquireLatestImage() + val buffer = image.planes[0].buffer + val bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + val bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + + val file = File(requireActivity().getExternalFilesDir(null), "selectedImage.jpg") + file.writeBytes(bytes) + imageFile = file + + requireActivity().runOnUiThread { + textureView.surfaceTexture?.let { _ -> + val canvas = textureView.lockCanvas() + if (canvas != null) { + canvas.drawBitmap(bitmapImage, 0f, 0f, null) + textureView.unlockCanvasAndPost(canvas) + } + } + } + image.close() + } + + private fun displayUploadedImage(uri: Uri) { + captureButton.visibility = View.INVISIBLE + galleryButton.visibility = View.INVISIBLE + recaptureButton.visibility = View.VISIBLE + confirmButton.visibility = View.VISIBLE + val imageView = view?.findViewById<ImageView>(R.id.imageView) + if (imageView != null) { + imageView.setImageURI(uri) + imageView.visibility = View.VISIBLE + textureView.visibility = View.GONE + } + + var parcelFileDescriptor: ParcelFileDescriptor? = null + try { + parcelFileDescriptor = requireActivity().contentResolver.openFileDescriptor(uri, "r") + val fileDescriptor = parcelFileDescriptor?.fileDescriptor + val file = File(requireActivity().cacheDir, "MyImage.jpg") + val inputStream = FileInputStream(fileDescriptor) + val outputStream = FileOutputStream(file) + inputStream.copyTo(outputStream) + imageFile = file + } catch (e: Exception) { + Log.e(ContentValues.TAG, e.message.toString()) + } finally { + try { + parcelFileDescriptor?.close() + } catch (e: IOException) { + Log.e(ContentValues.TAG, e.message.toString()) + } + } + + } + + private fun createPopUp(title: String, message: String) { + if (isAdded) { + val dialogBuilder = AlertDialog.Builder(requireActivity()) + dialogBuilder.setTitle(title) + dialogBuilder.setMessage(message) + + dialogBuilder.setPositiveButton("Ok") { _, _ -> + findNavController().navigate(R.id.transaction_fragment) + } + requireActivity().runOnUiThread { + dialogBuilder.show() + } + } + } + + private val getContent = + registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> + if (uri != null) { + displayUploadedImage(uri) + } + } + + private fun openCamera() { + + if (ActivityCompat.checkSelfPermission( + requireActivity(), + Manifest.permission.CAMERA + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + requireActivity(), CAMERA_PERMISSION, 0 + ) + return + } + cameraManager.openCamera( + cameraManager.cameraIdList[0], + object : CameraDevice.StateCallback() { + override fun onOpened(camera: CameraDevice) { + cameraDevice = camera + capReq = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) + val surface = Surface(textureView.surfaceTexture) + capReq.addTarget(surface) + val outputConfigurationSurface = OutputConfiguration(surface) + val outputConfigurationImageReader = OutputConfiguration(imageReader.surface) + val sessionConfiguration = SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + listOf(outputConfigurationSurface, outputConfigurationImageReader), + Executors.newSingleThreadExecutor(), + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(session: CameraCaptureSession) { + cameraCaptureSession = session + cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) + } + + override fun onConfigureFailed(session: CameraCaptureSession) { + + } + } + ) + + cameraDevice!!.createCaptureSession(sessionConfiguration) + } + + override fun onDisconnected(camera: CameraDevice) { + cameraDevice?.close() + } + + override fun onError(camera: CameraDevice, error: Int) { + cameraDevice?.close() + Log.e(ContentValues.TAG, "Camera error: $error") + } + + }, + handler + ) + } + + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> + permissions.forEach { (_, isGranted) -> + if (isGranted) { + textureView = view?.findViewById(R.id.textureView)!! + cameraManager = + requireActivity().getSystemService(Context.CAMERA_SERVICE) as CameraManager + handlerThread = HandlerThread("videoThread") + handlerThread.start() + handler = Handler(handlerThread.looper) + + textureView.surfaceTextureListener = + object : TextureView.SurfaceTextureListener { + override fun onSurfaceTextureAvailable( + surface: SurfaceTexture, + width: Int, + height: Int + ) { + openCamera() + } + + override fun onSurfaceTextureSizeChanged( + surface: SurfaceTexture, + width: Int, + height: Int + ) { + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + + return false + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { + } + + } + + imageReader = ImageReader.newInstance(1080, 1323, ImageFormat.JPEG, 1) + + captureButton.setOnClickListener { + captureButton.visibility = View.INVISIBLE + galleryButton.visibility = View.INVISIBLE + recaptureButton.visibility = View.VISIBLE + confirmButton.visibility = View.VISIBLE + capReq = + cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) + capReq.addTarget(imageReader.surface) + cameraCaptureSession.capture( + capReq.build(), + object : CameraCaptureSession.CaptureCallback() { + override fun onCaptureCompleted( + session: CameraCaptureSession, + request: CaptureRequest, + result: TotalCaptureResult + ) { + super.onCaptureCompleted(session, request, result) + displayCapturedImage() + } + }, + null + ) + } + + + recaptureButton.setOnClickListener { + captureButton.visibility = View.VISIBLE + galleryButton.visibility = View.VISIBLE + recaptureButton.visibility = View.INVISIBLE + confirmButton.visibility = View.INVISIBLE + textureView.visibility = View.VISIBLE + requireView().findViewById<ImageView>(R.id.imageView).visibility = View.GONE + configureCameraPreview() + } + + + galleryButton.setOnClickListener { + getContent.launch("image/*") + } + + confirmButton.setOnClickListener { + job = CoroutineScope(Dispatchers.IO).launch { + val sharedPreferences = + requireActivity().getSharedPreferences( + "sharedPrefs", + Context.MODE_PRIVATE + ) + val token = sharedPreferences.getString("TOKEN", "") ?: "" + try { + val requestFile = RequestBody.create( + MediaType.parse("image/jpg"), + imageFile + ) + val body = MultipartBody.Part.createFormData( + "file", + imageFile.name, + requestFile + ) + val response = + RetrofitInstance.api.uploadBill("Bearer $token", body) + println("Bearer $token") + if (response.isSuccessful) { + val responseBody = response.body() + println(responseBody) + createPopUp( + "Berhasil", + "Transaksi berhasil ditambahkan" + ) + } else { + createPopUp( + "Gagal", + "Nota gagal dibaca. Error: ${ + response.errorBody()?.toString() + }" + ) + } + } catch (e: HttpException) { + Log.e(ContentValues.TAG, e.message()) + } catch (e: Throwable) { + Log.e(ContentValues.TAG, e.stackTraceToString()) + } + + } + } + + } else { + val intent = Intent(requireActivity(), MainActivity::class.java) + startActivity(intent) + } + } + } + + + companion object { + private val CAMERA_PERMISSION = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.READ_MEDIA_IMAGES, + ) + } else { + arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ScannerFragment.kt b/app/src/main/java/com/example/bondoman/ScannerFragment.kt deleted file mode 100644 index a6163caa4d7348e6c452a17aa2a504ce45d717d6..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/ScannerFragment.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.bondoman - -import android.app.Activity -import android.os.Bundle -import android.view.LayoutInflater -import android.content.Intent -import android.graphics.Bitmap -import android.provider.MediaStore -import androidx.fragment.app.Fragment -import android.view.View -import android.view.ViewGroup - -class ScannerFragment : Fragment() { - val REQUEST_IMAGE_CAPTURE = 1 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - dispatchTakePictureIntent() - } - - private fun dispatchTakePictureIntent() { - Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> - takePictureIntent.resolveActivity(requireActivity().packageManager)?.also { - startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) { - val imageBitmap = data?.extras?.get("data") as Bitmap - // Handle the bitmap - } - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_scanner, container, false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/api/ApiInterface.kt b/app/src/main/java/com/example/bondoman/api/ApiInterface.kt index 647032e5078cb49f2725b5ac40c8c1655ae22ea9..e30da982079fde174120e03f0c33008bb1663349 100644 --- a/app/src/main/java/com/example/bondoman/api/ApiInterface.kt +++ b/app/src/main/java/com/example/bondoman/api/ApiInterface.kt @@ -3,13 +3,14 @@ package com.example.bondoman.api import com.example.bondoman.models.LoginRequest import com.example.bondoman.models.LoginResponse import com.example.bondoman.models.JWTResponse -import com.example.bondoman.models.ScanRequest import com.example.bondoman.models.ScanResponse +import okhttp3.MultipartBody import retrofit2.Response import retrofit2.http.POST import retrofit2.http.Body import retrofit2.http.Header -import java.io.File +import retrofit2.http.Multipart +import retrofit2.http.Part interface ApiInterface { @POST("api/auth/login") @@ -18,6 +19,10 @@ interface ApiInterface { @POST("/api/auth/token") suspend fun jwt(@Header("Authorization") token: String): Response<JWTResponse> - @POST("/api/bill/upload") - suspend fun scan(@Body scanRequest: ScanRequest): Response<ScanResponse> + @Multipart + @POST("api/bill/upload") + suspend fun uploadBill( + @Header("Authorization") token: String, + @Part file: MultipartBody.Part + ): Response<ScanResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/models/ScanRequest.kt b/app/src/main/java/com/example/bondoman/models/ScanRequest.kt deleted file mode 100644 index 33d84e16bd41a296dcb795b3177a0790d81e8252..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/models/ScanRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.bondoman.models - -import java.io.File - -data class ScanRequest ( - val token: String, - val file: File, - ) \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/models/ScanResponse.kt b/app/src/main/java/com/example/bondoman/models/ScanResponse.kt index d2a02e8edd6a02a8d0d64041a66c3b0eda81f16e..6e717c07154f48f89c0d6affe98cd253094c634b 100644 --- a/app/src/main/java/com/example/bondoman/models/ScanResponse.kt +++ b/app/src/main/java/com/example/bondoman/models/ScanResponse.kt @@ -1,7 +1,21 @@ package com.example.bondoman.models -data class ScanResponse ( +data class ScanResponse( + val items: Items +) { + override fun toString() = "Response(items=$items)" +} + +data class Items( + val items: List<Item> +) { + override fun toString() = "Items(items=$items)" +} + +data class Item( val name: String, - val quantity: Int, - val price: Int, -) \ No newline at end of file + val qty: Int, + val price: Double +) { + override fun toString() = "Item(name='$name', qty=$qty, price=$price)" +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_left_black.xml b/app/src/main/res/drawable/ic_arrow_left_black.xml new file mode 100644 index 0000000000000000000000000000000000000000..9edb1375a5117c337be687fb01b15d94edeb23ed --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left_black.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16.727,12.005H19.5" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M4.5,12.005H13.123" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M10.55,5.975C10.55,5.975 4.5,9.235 4.5,11.995C4.5,14.765 10.55,18.025 10.55,18.025" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> +</vector> diff --git a/app/src/main/res/drawable/ic_arrow_left_white.xml b/app/src/main/res/drawable/ic_arrow_left_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..1fbf3d563514db9c9c96feccf04979ef46182d89 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left_white.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16.727,12.005H19.5" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M4.5,12.005H13.123" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M10.55,5.975C10.55,5.975 4.5,9.235 4.5,11.995C4.5,14.765 10.55,18.025 10.55,18.025" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> +</vector> diff --git a/app/src/main/res/drawable/navbar_border.xml b/app/src/main/res/drawable/navbar_border.xml new file mode 100644 index 0000000000000000000000000000000000000000..9c282b97861458c799007738633d90a26e77962e --- /dev/null +++ b/app/src/main/res/drawable/navbar_border.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@android:color/transparent" /> + <stroke android:width="1dp" android:color="#000000" /> + <corners android:radius="20dp"/> +</shape> \ No newline at end of file diff --git a/app/src/main/res/drawable/navbar_bordered_background.xml b/app/src/main/res/drawable/navbar_bordered_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9879e51abbdfd0d98665a57dbdb023704069a35 --- /dev/null +++ b/app/src/main/res/drawable/navbar_bordered_background.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/navbar_background"/> + <item android:drawable="@drawable/navbar_border"/> +</layer-list> \ 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 9db9a1173c4415acf07045b13dffe48b190374d9..bb43b2ebeebada1688cb22b4896ef32b2d7ff0f8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,19 +6,34 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - <androidx.fragment.app.FragmentContainerView - android:id="@+id/nav_host_fragment" - android:name="androidx.navigation.fragment.NavHostFragment" + <include + layout="@layout/toolbar" + android:id="@+id/toolbar" android:layout_width="match_parent" - android:layout_height="match_parent" - app:defaultNavHost="true" - app:navGraph="@navigation/nav_graph" /> + android:layout_height="?attr/actionBarSize" + app:layout_constraintTop_toTopOf="parent"/> - <include - layout="@layout/navbar_layout" + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" /> + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintBottom_toBottomOf="parent"> + <androidx.fragment.app.FragmentContainerView + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_constraintTop_toTopOf="parent" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" /> + + <include + android:id="@+id/navigation_bar" + layout="@layout/navbar_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" /> + </androidx.constraintlayout.widget.ConstraintLayout> <!-- <FrameLayout--> <!-- android:id="@+id/fragment_container"--> <!-- android:layout_width="match_parent"--> diff --git a/app/src/main/res/layout/activity_scan.xml b/app/src/main/res/layout/fragment_scan.xml similarity index 87% rename from app/src/main/res/layout/activity_scan.xml rename to app/src/main/res/layout/fragment_scan.xml index c4e2d49599a6bf2228c86c168516654820165158..90ccbe6eefecbcc8c160d85013b5bff03a425823 100644 --- a/app/src/main/res/layout/activity_scan.xml +++ b/app/src/main/res/layout/fragment_scan.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" @@ -13,14 +12,24 @@ android:layout_height="435dp" android:layout_marginEnd="30dp" android:layout_marginStart="30dp" - android:layout_marginTop="100dp" + android:layout_marginTop="10dp" android:layout_marginBottom="200dp" android:orientation="horizontal" + android:layout_centerHorizontal="true" android:background="@drawable/effect_rounded_edge"> <TextureView android:layout_width="match_parent" android:layout_height="match_parent" + android:id="@+id/textureView"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" + android:adjustViewBounds="true" + android:contentDescription="@string/selected_image_description" + android:id="@+id/imageView" + android:visibility="gone"/> <View android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/app/src/main/res/layout/navbar_layout.xml b/app/src/main/res/layout/navbar_layout.xml index 923eac33e7cadfaacf5ac029ea757735831492c0..59eb3e438134096ba4696275c1b55392e251da66 100644 --- a/app/src/main/res/layout/navbar_layout.xml +++ b/app/src/main/res/layout/navbar_layout.xml @@ -14,6 +14,7 @@ app:layout_constraintStart_toStartOf="parent"> <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="60dp" diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c9e16fa507e68dfc84fa0270128af7a6a35b3d0 --- /dev/null +++ b/app/src/main/res/layout/toolbar.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="@android:color/transparent"> + <ImageButton + android:id="@+id/toolbar_back_button" + android:layout_width="?attr/actionBarSize" + android:layout_height="match_parent" + android:src="@drawable/ic_arrow_left_white" + android:background="@android:color/transparent" + android:layout_alignParentStart="true" + android:contentDescription="@string/back_button_description"/> + <TextView + android:id="@+id/toolbar_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:textColor="@color/white" + android:layout_centerInParent="true"/> + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 98d14d3364b40fb2aa9f45340e0285afc9dbbf4e..137a3668e0fa81cb75f8cc971293e272ac7fe347 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -16,9 +16,16 @@ tools:layout="@layout/fragment_grafik" android:name="com.example.bondoman.GraphFragment"> </fragment> - <fragment android:id="@+id/setting_fragment" + <fragment + android:id="@+id/setting_fragment" android:label="@string/setting_button_description" tools:layout="@layout/fragment_setting" android:name="com.example.bondoman.SettingFragment"> </fragment> + <fragment + android:id="@+id/scan_fragment" + android:name="com.example.bondoman.ScanFragment" + android:label="@string/scan_button_description" + tools:layout="@layout/fragment_scan"> + </fragment> </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 6e8065f365353a550f8c0dbdb94b4d38316e599a..0b2acbf6008b4ca603433365e5dc0463897cd677 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,11 +11,14 @@ <string name="rescan_button_description">Pindai Ulang</string> <string name="image_button_description">Galeri</string> <string name="confirm_button_description">Konfirmasi</string> - <!-- TODO: Remove or change this placeholder text --> <string name="hello_blank_fragment">Hello blank fragment</string> <string-array name="category_array"> <item>Pemasukan</item> <item>Pengeluaran</item> </string-array> + <string name="back_button_description">Kembali</string> + <string name="selected_image_description">Gambar Terpilih</string> + <string name="scan_fragment_title">Scan Nota</string> + </resources> \ No newline at end of file