diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000000000000000000000000000000000..7643783a82f60b3b876fe58a9314fb50520df486 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ +<component name="ProjectCodeStyleConfiguration"> + <code_scheme name="Project" version="173"> + <JetCodeStyleSettings> + <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> + </JetCodeStyleSettings> + <codeStyleSettings language="XML"> + <option name="FORCE_REARRANGE_MODE" value="1" /> + <indentOptions> + <option name="CONTINUATION_INDENT_SIZE" value="4" /> + </indentOptions> + <arrangement> + <rules> + <section> + <rule> + <match> + <AND> + <NAME>xmlns:android</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>^$</XML_NAMESPACE> + </AND> + </match> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>xmlns:.*</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>^$</XML_NAMESPACE> + </AND> + </match> + <order>BY_NAME</order> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>.*:id</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> + </AND> + </match> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>.*:name</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> + </AND> + </match> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>name</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>^$</XML_NAMESPACE> + </AND> + </match> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>style</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>^$</XML_NAMESPACE> + </AND> + </match> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>.*</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>^$</XML_NAMESPACE> + </AND> + </match> + <order>BY_NAME</order> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>.*</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> + </AND> + </match> + <order>ANDROID_ATTRIBUTE_ORDER</order> + </rule> + </section> + <section> + <rule> + <match> + <AND> + <NAME>.*</NAME> + <XML_ATTRIBUTE /> + <XML_NAMESPACE>.*</XML_NAMESPACE> + </AND> + </match> + <order>BY_NAME</order> + </rule> + </section> + </rules> + </arrangement> + </codeStyleSettings> + <codeStyleSettings language="kotlin"> + <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> + </codeStyleSettings> + </code_scheme> +</component> \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000000000000000000000000000000000..79ee123c2b23e069e35ed634d687e17f731cc702 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ +<component name="ProjectCodeStyleConfiguration"> + <state> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </state> +</component> \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/fragment/ui/twibbon/TwibbonFragment.kt b/app/src/main/java/itb/bos/bondoman/fragment/ui/twibbon/TwibbonFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..9a86e5242b02ca759d735b18a9d133831750942e --- /dev/null +++ b/app/src/main/java/itb/bos/bondoman/fragment/ui/twibbon/TwibbonFragment.kt @@ -0,0 +1,298 @@ +package itb.bos.bondoman.fragment.ui.twibbon + +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Matrix +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.Toast +import androidx.camera.core.Camera +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.google.common.util.concurrent.ListenableFuture +import itb.bos.bondoman.Manifest +import itb.bos.bondoman.R +import itb.bos.bondoman.databinding.FragmentTwibbonBinding + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * Use the [TwibbonFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class TwibbonFragment : Fragment() { + // TODO: Rename and change types of parameters + //izin berkamera + private val CAMERA_REQUEST__CODE_PERMISSIONS = 10 + private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) + + private var _binding: FragmentTwibbonBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + private lateinit var viewer: PreviewView + private lateinit var captureButton: Button + private var imageCapture: ImageCapture? = null + private lateinit var twibbonViewer: ImageView + private var camera: Camera? = null + private lateinit var twibbonViewModel:TwibbonViewModel + private lateinit var retakePhotoButton: Button + private lateinit var switchCameraButton: Button + private lateinit var preview: Preview + private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider> + + private lateinit var cameraSelector: CameraSelector + + private lateinit var TWIBBON: Bitmap + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + TWIBBON = BitmapFactory.decodeResource(resources, R.drawable.twibbon) + twibbonViewModel = + ViewModelProvider(this)[TwibbonViewModel::class.java] + + _binding = FragmentTwibbonBinding.inflate(inflater, container, false) + val root: View = binding.root + viewer = binding.surfaceView + captureButton = binding.twibbonButton + twibbonViewer = binding.imageViewer + switchCameraButton = binding.flipCameraButton + //tambahin listenernya + switchCameraButton.setOnClickListener { + switchCamera() + } + retakePhotoButton = binding.retakePhoto + //tambahin listenernya + retakePhotoButton.setOnClickListener { + //tutup viewer sekarang + twibbonViewer.visibility = View.GONE + //tampilin halaman awal + viewer.visibility = View.VISIBLE + captureButton.visibility = View.VISIBLE + //tutup tombol ini + retakePhotoButton.visibility = View.GONE + //balikin tombol switch camera + switchCameraButton.visibility = View.VISIBLE + //hapus twibbon + twibbonViewer.setImageBitmap(null) + } + //tambahin listener + captureButton.setOnClickListener { + captureImage() + } + twibbonViewModel.text.observe(viewLifecycleOwner) { + } + twibbonViewModel._bitmap.observe(viewLifecycleOwner) { + //tampilin gambar + twibbonViewer.setImageBitmap(twibbonViewModel._bitmap.value) + Log.v("IMAGE", twibbonViewModel._bitmap.value.toString()) + } + //tunggu previewview selesai diinit + // setting data ulang + twibbonViewer.visibility = View.GONE + twibbonViewer.setImageBitmap(null) + viewer.visibility = View.VISIBLE + //cek izin + if (!isPermissionGranted()) { + ActivityCompat.requestPermissions( + requireActivity(), + REQUIRED_PERMISSIONS, CAMERA_REQUEST__CODE_PERMISSIONS + ) + } else { + setupCamera() + } + return root + } + + private fun switchCamera() { + cameraSelector = if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) { + CameraSelector.DEFAULT_FRONT_CAMERA + } else { + CameraSelector.DEFAULT_BACK_CAMERA + } + preview = Preview.Builder().build().also { + it.setSurfaceProvider(viewer.surfaceProvider) + } + imageCapture = ImageCapture.Builder().setTargetRotation(viewer.display.rotation).build() + try { + camera?.let { + preview.setSurfaceProvider(null) + } + cameraProviderFuture.get().unbindAll() + camera?.let { + preview.setSurfaceProvider(viewer.surfaceProvider) + } + camera = cameraProviderFuture.get() + .bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture) + } catch (e: Error) { + Log.e("CAMERA", "Error: $e.localizedMessage") + } + + } + fun rotateImage(source: Bitmap, angle: Float): Bitmap? { + val matrix = Matrix() + matrix.postRotate(angle) + return Bitmap.createBitmap( + source, 0, 0, source.width, source.height, + matrix, true + ) + } + fun scaleImage(source:Bitmap,scaleX:Float,scaleY:Float):Bitmap?{ + val matrix = Matrix() + matrix.postScale(scaleX,scaleY) + return Bitmap.createBitmap( + source, 0, 0, source.width, source.height, + matrix, true + ) + } + + private fun captureImage() { + getBitmapFromImage() + viewer.visibility = View.GONE + twibbonViewer.setImageBitmap(twibbonViewModel._bitmap.value) + twibbonViewer.visibility = View.VISIBLE + retakePhotoButton.visibility = View.VISIBLE + captureButton.visibility = View.GONE + switchCameraButton.visibility = View.GONE + } + + private fun getBitmapFromImage() { + imageCapture?.takePicture(ContextCompat.getMainExecutor(requireContext()), + object : ImageCapture.OnImageCapturedCallback() { + override fun onCaptureSuccess(image: ImageProxy) { + val buffer = image.planes[0].buffer + val bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + val bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + Log.v("IMAGE", bitmapImage.byteCount.toString()) + val orientation = resources.configuration.orientation + var capturedImage:Bitmap? = null + if(cameraSelector==CameraSelector.DEFAULT_BACK_CAMERA){ + if(orientation==Configuration.ORIENTATION_PORTRAIT){ + capturedImage = rotateImage(bitmapImage, 90.0f) + } + else{ + capturedImage = rotateImage(bitmapImage, 180.0f) + } + } + else{ + if(orientation==Configuration.ORIENTATION_PORTRAIT){ + capturedImage = rotateImage(bitmapImage, 90.0f) + capturedImage = scaleImage(capturedImage!!,-1f,1f) + } + else{ + //landscape + capturedImage = rotateImage(bitmapImage, 180.0f) + capturedImage = scaleImage(capturedImage!!,1f,-1f) + } + } + Log.v("TWIBBON", capturedImage.toString()) + twibbonViewModel._bitmap.value = applyTwibbon(capturedImage!!) + image.close() + } + + override fun onError(exception: ImageCaptureException) { + Log.e("IMAGE", "Error capturing image") + } + }) + } + + private fun applyTwibbon(image: Bitmap): Bitmap { + val result: Bitmap = Bitmap.createBitmap(image.width, image.height, image.config) + val twibbonCanvas = Canvas(result) + val Resized_Twibbon = Bitmap.createScaledBitmap(TWIBBON, image.width, image.height, true) + twibbonCanvas.drawBitmap(image, 0f, 0f, null) + twibbonCanvas.drawBitmap(Resized_Twibbon, 0f, 0f, null) + return result + } + + private fun setupCamera() { + cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + cameraProviderFuture.addListener({ + preview = Preview.Builder().build().also { + it.setSurfaceProvider(viewer.surfaceProvider) + } + imageCapture = ImageCapture.Builder() + .setTargetRotation(viewer.display.rotation) + .build() + cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + try { + camera?.let { + preview.setSurfaceProvider(null) + } + cameraProviderFuture.get().unbindAll() + camera = cameraProviderFuture.get() + .bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture) + } catch (e: Error) { + Log.e("CAMERA", "$e.localizedMessage") + } + }, ContextCompat.getMainExecutor(requireContext())) + } + + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + + fun isPermissionGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission(requireContext(), it) == PackageManager.PERMISSION_GRANTED + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array<out String>, + grantResults: IntArray + ) { + if (requestCode == CAMERA_REQUEST__CODE_PERMISSIONS) { + if (isPermissionGranted()) { + setupCamera() + } else { + Toast.makeText(context, "Izin Membuka Kamera Tidak Diberikan!", Toast.LENGTH_SHORT) + .show() + requireActivity().finish() + } + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + } + + override fun onDestroy() { + super.onDestroy() + } + + override fun onResume() { + super.onResume() + } +} \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/fragment/ui/twibbon/TwibbonViewModel.kt b/app/src/main/java/itb/bos/bondoman/fragment/ui/twibbon/TwibbonViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..3b7385b25fa87d3f71e7c4dfed5751f1dce1f045 --- /dev/null +++ b/app/src/main/java/itb/bos/bondoman/fragment/ui/twibbon/TwibbonViewModel.kt @@ -0,0 +1,17 @@ +package itb.bos.bondoman.fragment.ui.twibbon +import android.graphics.Bitmap +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class TwibbonViewModel : ViewModel() { + + private val _text = MutableLiveData<String>().apply { + value = "This is twibbon Fragment" + } + val text: LiveData<String> = _text + + val _bitmap = MutableLiveData<Bitmap?>() + private val bitmap: LiveData<Bitmap?> + get() = _bitmap +} \ No newline at end of file diff --git a/app/src/main/res/drawable/twibbon.jpg b/app/src/main/res/drawable/twibbon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5481b016535a45f9e1d3083b5fef32d2a14ff9e Binary files /dev/null and b/app/src/main/res/drawable/twibbon.jpg differ diff --git a/app/src/main/res/layout/fragment_twibbon.xml b/app/src/main/res/layout/fragment_twibbon.xml new file mode 100644 index 0000000000000000000000000000000000000000..96502bdddb0b225ac3a87b2b2bd599950ba7075d --- /dev/null +++ b/app/src/main/res/layout/fragment_twibbon.xml @@ -0,0 +1,76 @@ +<?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=".ui.twibbon.TwibbonFragment"> + + <LinearLayout + android:id="@+id/imageLayout" + android:layout_width="match_parent" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@+id/buttonLayout"> + <androidx.camera.view.PreviewView + android:id="@+id/surfaceView" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + + <ImageView + android:id="@+id/imageViewer" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone"/> + </LinearLayout> + <LinearLayout + android:id="@+id/buttonLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintTop_toBottomOf="@+id/imageLayout" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginTop="18dp" + android:layout_marginBottom="54dp"> + <Button + android:id="@+id/twibbonButton" + android:layout_weight=".5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:text="@string/twibbonize" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + android:drawableTint="@color/white" + /> + <Button + android:id="@+id/retakePhoto" + android:layout_weight=".5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:text="@string/retake" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + android:visibility="gone" + android:drawableTint="@color/white" + /> + + <Button + android:id="@+id/flipCameraButton" + android:layout_weight=".5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:text="@string/flip_twibbon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + android:drawableTint="@color/white" + /> + </LinearLayout> +</androidx.constraintlayout.widget.ConstraintLayout> \ 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 1a87f450744bfb16b6106255746a5bb1c61eabab..e234b6df57ecab7384baf18fb593d64edf447bda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,5 +17,8 @@ </string-array> <!-- TODO: Remove or change this placeholder text --> <string name="hello_blank_fragment">Hello blank fragment</string> + <string name="flip_twibbon">Flip Twibbon</string> + <string name="twibbonize">Twibbonize</string> + <string name="retake">Retake</string> </resources>