diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c04f7714b4f142637d9bcb6e064abcff79fd130..5d86b9b2b9c4988816841c2afafe60adaefe9393 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,5 +66,10 @@ dependencies { implementation ("org.apache.poi:poi-ooxml:5.2.4") implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") implementation ("com.squareup.okhttp3:okhttp:4.9.1") + + implementation ("androidx.camera:camera-core:1.2.2") + implementation ("androidx.camera:camera-camera2:1.2.2") + implementation ("androidx.camera:camera-lifecycle:1.2.2") + implementation ("androidx.camera:camera-view:1.2.2") implementation ("com.google.android.gms:play-services-location:18.0.0") } \ No newline at end of file diff --git a/app/src/main/java/com/example/android_hit/FragmentTwibbon.kt b/app/src/main/java/com/example/android_hit/FragmentTwibbon.kt new file mode 100644 index 0000000000000000000000000000000000000000..57272afd5544051085322b46fae2139095c5c381 --- /dev/null +++ b/app/src/main/java/com/example/android_hit/FragmentTwibbon.kt @@ -0,0 +1,145 @@ +package com.example.android_hit + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.content.IntentFilter +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageFormat +import android.graphics.Matrix +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.Toast +import androidx.camera.core.CameraSelector +import androidx.camera.core.ExperimentalGetImage +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.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.example.android_hit.databinding.FragmentTwibbonBinding +import com.example.android_hit.room.TransactionDB +import com.example.android_hit.room.TransactionEntity +import com.google.common.util.concurrent.ListenableFuture +import java.nio.ByteBuffer +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class FragmentTwibbon : Fragment() { + private var _binding: FragmentTwibbonBinding? = null + private val binding get() = _binding!! + private lateinit var cameraProvider: ProcessCameraProvider + private lateinit var imageCapture: ImageCapture + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentTwibbonBinding.inflate(inflater, container, false) + return binding.root + } + + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupCamera() + + binding.capture.setOnClickListener { + captureImage() + } + + binding.retakeButton.setOnClickListener { + retakeImage() + } + } + + private fun setupCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + cameraProviderFuture.addListener(Runnable { + cameraProvider = cameraProviderFuture.get() + val preview: Preview = Preview.Builder().build() + + val cameraSelector: CameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + + preview.setSurfaceProvider(binding.previewView.surfaceProvider) + + imageCapture = ImageCapture.Builder() + .setTargetRotation(requireView().display.rotation) + .build() + + cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, preview) + }, ContextCompat.getMainExecutor(requireContext())) + } + + private fun captureImage() { + 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 bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.size) + val rotatebitmap = rotateBitmap(bitmap) + displayCapturedImage(rotatebitmap) + image.close() + cameraProvider.unbindAll() + } + + override fun onError(exception: ImageCaptureException) { + // Handle capture error + Toast.makeText(requireContext(), "Capture failed: ${exception.message}", Toast.LENGTH_SHORT).show() + } + }) + } + + private fun retakeImage() { + // Clear the captured image and hide the retake button + binding.imageView.setImageBitmap(null) + binding.retakeButton.visibility = View.GONE + // Show capture button + binding.capture.visibility = View.VISIBLE + binding.previewView.visibility = View.VISIBLE + binding.imageView.visibility = View.GONE + setupCamera() + } + + private fun displayCapturedImage(bitmap: Bitmap?) { + binding.imageView.setImageBitmap(bitmap) + binding.imageView.scaleType = ImageView.ScaleType.CENTER_CROP + + // Show/hide appropriate views + binding.retakeButton.visibility = View.VISIBLE + binding.previewView.visibility = View.GONE + binding.capture.visibility = View.GONE + binding.imageView.visibility = View.VISIBLE + } + + fun rotateBitmap(bitmap: Bitmap): Bitmap { + val matrix = Matrix() + matrix.postRotate(90f) + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + cameraProvider.unbindAll() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android_hit/HeaderTwibbon.kt b/app/src/main/java/com/example/android_hit/HeaderTwibbon.kt new file mode 100644 index 0000000000000000000000000000000000000000..ed71eb162eb3f540d87944ce7f2a7cc657a1340d --- /dev/null +++ b/app/src/main/java/com/example/android_hit/HeaderTwibbon.kt @@ -0,0 +1,59 @@ +package com.example.android_hit + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +// 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 [HeaderTwibbon.newInstance] factory method to + * create an instance of this fragment. + */ +class HeaderTwibbon : Fragment() { + // TODO: Rename and change types of parameters + private var param1: String? = null + private var param2: String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + param1 = it.getString(ARG_PARAM1) + param2 = it.getString(ARG_PARAM2) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_header_twibbon, container, false) + } + + companion object { + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param param1 Parameter 1. + * @param param2 Parameter 2. + * @return A new instance of fragment HeaderTwibbon. + */ + // TODO: Rename and change types and number of parameters + @JvmStatic + fun newInstance(param1: String, param2: String) = + HeaderTwibbon().apply { + arguments = Bundle().apply { + putString(ARG_PARAM1, param1) + putString(ARG_PARAM2, param2) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android_hit/MainActivity.kt b/app/src/main/java/com/example/android_hit/MainActivity.kt index 80a96b5e03481e2115d43e0924555b2e0838f4be..0488bc5beb7f0a33973780139a54a7a9261a31e9 100644 --- a/app/src/main/java/com/example/android_hit/MainActivity.kt +++ b/app/src/main/java/com/example/android_hit/MainActivity.kt @@ -74,6 +74,9 @@ class MainActivity : AppCompatActivity() { R.id.nav_settings -> { setCurrentFragment(Settings(), HeaderSettings()) } + R.id.nav_twibbon -> { + setCurrentFragment(FragmentTwibbon(), HeaderTwibbon()) + } } true } @@ -82,6 +85,7 @@ class MainActivity : AppCompatActivity() { when (item.itemId) { R.id.nav_transaction -> setCurrentFragment(Transaction(), HeaderTransaction()) R.id.nav_scan -> setCurrentFragment(Scan(), HeaderScan()) + R.id.nav_twibbon -> setCurrentFragment(FragmentTwibbon(), HeaderTwibbon()) R.id.nav_graphs -> setCurrentFragment(Graphs(), HeaderGraphs()) R.id.nav_settings -> setCurrentFragment(Settings(), HeaderSettings()) } diff --git a/app/src/main/res/drawable/baseline_photo_camera_24.xml b/app/src/main/res/drawable/baseline_photo_camera_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..aa207af7b1e59373a0af433ea8fb2ea82ac0ffc8 --- /dev/null +++ b/app/src/main/res/drawable/baseline_photo_camera_24.xml @@ -0,0 +1,6 @@ +<vector android:height="24dp" android:tint="#322F50" + 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="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> + <path android:fillColor="@android:color/white" android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/> +</vector> diff --git a/app/src/main/res/drawable/baseline_refresh_24.xml b/app/src/main/res/drawable/baseline_refresh_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..70296044e8e9676e7f695acd4af95486a3c14e5b --- /dev/null +++ b/app/src/main/res/drawable/baseline_refresh_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#322F50" + 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="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/> +</vector> diff --git a/app/src/main/res/drawable/twibbon.png b/app/src/main/res/drawable/twibbon.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b89661c5c3b23784dc984764a4bae3d0130872 Binary files /dev/null and b/app/src/main/res/drawable/twibbon.png differ diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 7de35322adf041fa063c1f6d0be3967b963c67d7..c1df0abc90ac3f2ce99705583c987bf4f2752ef8 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -23,7 +23,7 @@ app:itemIconTint="@drawable/nav_item_color_selected" app:itemTextColor="@color/primary_color_1" app:labelVisibilityMode="labeled" - app:itemPaddingBottom="36dp" + app:itemPaddingBottom="12dp" app:menu="@menu/side_nav_menu" /> diff --git a/app/src/main/res/layout/fragment_header_twibbon.xml b/app/src/main/res/layout/fragment_header_twibbon.xml new file mode 100644 index 0000000000000000000000000000000000000000..17a88a37a07915ea9ccea077b3911115ddd3f346 --- /dev/null +++ b/app/src/main/res/layout/fragment_header_twibbon.xml @@ -0,0 +1,51 @@ +<?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" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/primary_color_4" + > + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:background="@drawable/rounded_background" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <LinearLayout + android:id="@+id/container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <ImageView + android:id="@+id/imageView" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:src="@drawable/baseline_photo_camera_24" /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + android:text="Twibbon" + android:textColor="@color/primary_color_1" + android:textSize="25sp" + android:textStyle="bold" /> + + </LinearLayout> + </androidx.constraintlayout.widget.ConstraintLayout> + +</androidx.constraintlayout.widget.ConstraintLayout> 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..2dd0881ffe8cf9583e06bf3dcb6b910b39695760 --- /dev/null +++ b/app/src/main/res/layout/fragment_twibbon.xml @@ -0,0 +1,65 @@ +<?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=".FragmentTwibbon"> + + + <FrameLayout + android:id="@+id/frameLayout" + android:layout_width="match_parent" + android:layout_height="500dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <androidx.camera.view.PreviewView + android:id="@+id/previewView" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <ImageView + android:id="@+id/imageView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerCrop" + android:visibility="gone" /> + + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@drawable/twibbon" /> + </FrameLayout> + + <ImageButton + android:id="@+id/capture" + android:layout_width="60sp" + android:layout_height="60sp" + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" + android:background="?attr/selectableItemBackgroundBorderless" + android:scaleType="centerCrop" + android:src="@drawable/rounded_button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/frameLayout" /> + + + <ImageButton + android:id="@+id/retakeButton" + android:layout_width="60sp" + android:layout_height="60sp" + android:layout_alignParentBottom="true" + android:background="?attr/selectableItemBackgroundBorderless" + android:src="@drawable/baseline_refresh_24" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/capture" + app:layout_constraintTop_toBottomOf="@+id/frameLayout" + app:layout_constraintStart_toStartOf="parent"/> + + +</androidx.constraintlayout.widget.ConstraintLayout> \ 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 7f94defe7d71235a8d6f0b45fa830652a522456e..6dfe8e2d69415af977732948f31a019338c10c9a 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -5,16 +5,20 @@ android:icon="@drawable/transaction_icon" android:title="Transaction" /> + <item + android:id="@+id/nav_graphs" + android:icon="@drawable/graphs_icon" + android:title="Graphs" + /> <item android:id="@+id/nav_scan" android:icon="@drawable/scan_icon" android:title="Scan" /> <item - android:id="@+id/nav_graphs" - android:icon="@drawable/graphs_icon" - android:title="Graphs" - /> + android:id="@+id/nav_twibbon" + android:icon="@drawable/baseline_photo_camera_24" + android:title="Twibbon"/> <item android:id="@+id/nav_settings" android:icon="@drawable/settings_icon" diff --git a/app/src/main/res/menu/side_nav_menu.xml b/app/src/main/res/menu/side_nav_menu.xml index a99b54a6082e8d6adf1c4966c1bf5032f6aa428b..2c38a20fd6169c97dcc1b9dcd4a5b5cb3648ca5c 100644 --- a/app/src/main/res/menu/side_nav_menu.xml +++ b/app/src/main/res/menu/side_nav_menu.xml @@ -4,14 +4,17 @@ android:id="@+id/nav_transaction" android:icon="@drawable/transaction_icon" /> + <item + android:id="@+id/nav_graphs" + android:icon="@drawable/graphs_icon" + /> <item android:id="@+id/nav_scan" android:icon="@drawable/scan_icon" /> <item - android:id="@+id/nav_graphs" - android:icon="@drawable/graphs_icon" - /> + android:id="@+id/nav_twibbon" + android:icon="@drawable/baseline_photo_camera_24" /> <item android:id="@+id/nav_settings" android:icon="@drawable/settings_icon"