diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c5b147c31e0d5c4a7aea53b41936dbccd4f5dcf8..b17c560ce87d64a0eac87478dfe9988f90606a04 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -58,6 +58,7 @@ dependencies {
     implementation(libs.camera.lifecycle)
     implementation(libs.camera.view)
     implementation(libs.glide)
+    implementation(libs.androidx.exifinterface)
     annotationProcessor(libs.glideCompiler)
     implementation(libs.androidx.activity)
     implementation(libs.play.services.location)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c3af3ac6c9e52e8c623da9d2cdec450a3779b2a9..155886d1acf6d0f5a8ddb5871f1ddf817f0f86a2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,7 +5,6 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-feature android:name="android.hardware.camera.any" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
diff --git a/app/src/main/java/com/example/abe/MainActivity.kt b/app/src/main/java/com/example/abe/MainActivity.kt
index 1c2c332d9497191ed587f3585c6f5a2da6ce1b49..a9a6ee06a40f9c83ac564cb8f1d485c27eff4037 100644
--- a/app/src/main/java/com/example/abe/MainActivity.kt
+++ b/app/src/main/java/com/example/abe/MainActivity.kt
@@ -6,6 +6,7 @@ import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.ActivityInfo
 import android.os.Bundle
 import android.view.View
 import android.widget.Toast
@@ -93,6 +94,7 @@ class MainActivity : AppCompatActivity(), ExportAlertDialogFragment.ExportAlertD
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
         preferenceDataStoreHelper = PreferenceDataStoreHelper(applicationContext)
 
         binding = ActivityMainBinding.inflate(layoutInflater)
@@ -107,6 +109,7 @@ class MainActivity : AppCompatActivity(), ExportAlertDialogFragment.ExportAlertD
                 R.id.navigation_transactions,
                 R.id.navigation_graph,
                 R.id.navigation_settings,
+                R.id.navigation_twibbon,
                 R.id.navigation_scan
             )
         )
@@ -116,6 +119,9 @@ class MainActivity : AppCompatActivity(), ExportAlertDialogFragment.ExportAlertD
         navController.addOnDestinationChangedListener { _, destination, _ ->
             if (destination.id == R.id.navigation_form_transaction) navView.visibility = View.GONE
             else navView.visibility = View.VISIBLE
+
+            if (destination.id == R.id.navigation_graph) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+            else requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
         }
 
         LocalBroadcastManager.getInstance(this).registerReceiver(br, filter)
diff --git a/app/src/main/java/com/example/abe/MainActivityViewModel.kt b/app/src/main/java/com/example/abe/MainActivityViewModel.kt
index d65253b4cee4f5aa229e11536afe552a8103f4db..afc842dc687d9c585fc6be5a53733c739eeb6b47 100644
--- a/app/src/main/java/com/example/abe/MainActivityViewModel.kt
+++ b/app/src/main/java/com/example/abe/MainActivityViewModel.kt
@@ -47,7 +47,7 @@ class MainActivityViewModel(private val transactionRepository: TransactionReposi
 
     suspend fun createEmailIntent(context: Context, user: String): Intent {
         clearExportCacheFiles(context)
-        val newFile = File(context.externalCacheDir, if (newExcelFormat) "transaction-export.xlsx" else "transaction-export.xls")
+        val newFile = File(context.cacheDir, if (newExcelFormat) "transaction-export.xlsx" else "transaction-export.xls")
         val contentUri =
             FileProvider.getUriForFile(context, "com.example.abe.fileprovider", newFile)
         exportTransactionsToExcel(context.contentResolver, contentUri, user)
@@ -66,7 +66,7 @@ class MainActivityViewModel(private val transactionRepository: TransactionReposi
     }
 
     fun clearExportCacheFiles(context: Context) {
-        context.externalCacheDir?.apply {
+        context.cacheDir?.apply {
             val files = listFiles() ?: emptyArray()
             files.forEach { file ->
                 if (file.name.startsWith("export"))
diff --git a/app/src/main/java/com/example/abe/data/TransactionDAO.kt b/app/src/main/java/com/example/abe/data/TransactionDAO.kt
index 3dda96965e6c03661ab3d23607972eefa1c26989..51a67245beff268dcfaac8253549c7f41922f448 100644
--- a/app/src/main/java/com/example/abe/data/TransactionDAO.kt
+++ b/app/src/main/java/com/example/abe/data/TransactionDAO.kt
@@ -22,10 +22,10 @@ interface TransactionDAO {
     @Query("DELETE FROM transactions")
     suspend fun deleteAll()
 
-    @Query("SELECT * FROM transactions WHERE email = :email")
+    @Query("SELECT * FROM transactions WHERE email = :email ORDER BY timestamp DESC")
     fun getAllObservable(vararg email: String): LiveData<List<Transaction>>
 
-    @Query("SELECT * FROM transactions WHERE email = :email")
+    @Query("SELECT * FROM transactions WHERE email = :email ORDER BY timestamp DESC")
     suspend fun getAll(vararg email: String): List<Transaction>
 
     @Query("SELECT * FROM transactions WHERE id = :id")
diff --git a/app/src/main/java/com/example/abe/data/network/Retrofit.kt b/app/src/main/java/com/example/abe/data/network/Retrofit.kt
index bd9b8605b78f7b6f4714a7d04256661d3cb9bc53..21536640fdf2b360b5306ab1bbbe4817ef1b918c 100644
--- a/app/src/main/java/com/example/abe/data/network/Retrofit.kt
+++ b/app/src/main/java/com/example/abe/data/network/Retrofit.kt
@@ -126,7 +126,6 @@ class Retrofit {
             }
 
             override fun onFailure(call: Call<ItemsRoot>, t: Throwable) {
-                Log.d("ABE-PHO", "Failed to send request: " + t.message)
                 callback.onFailure("Failed to send photo")
             }
         })
diff --git a/app/src/main/java/com/example/abe/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/example/abe/ui/dashboard/DashboardFragment.kt
deleted file mode 100644
index 3351e83a456c4a58c40e979e850d5e1650c81ece..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/example/abe/ui/dashboard/DashboardFragment.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.example.abe.ui.dashboard
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import com.example.abe.databinding.FragmentDashboardBinding
-
-class DashboardFragment : Fragment() {
-
-    private var _binding: FragmentDashboardBinding? = null
-
-    // This property is only valid between onCreateView and
-    // onDestroyView.
-    private val binding get() = _binding!!
-
-    override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
-    ): View {
-        val dashboardViewModel =
-                ViewModelProvider(this).get(DashboardViewModel::class.java)
-
-        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
-        val root: View = binding.root
-
-        val textView: TextView = binding.textDashboard
-        dashboardViewModel.text.observe(viewLifecycleOwner) {
-            textView.text = it
-        }
-        return root
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        _binding = null
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/abe/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/example/abe/ui/dashboard/DashboardViewModel.kt
deleted file mode 100644
index 592e6697da608c2eec0cc5e3380e899d68aa99b2..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/example/abe/ui/dashboard/DashboardViewModel.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.example.abe.ui.dashboard
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-
-class DashboardViewModel : ViewModel() {
-
-    private val _text = MutableLiveData<String>().apply {
-        value = "This is dashboard Fragment"
-    }
-    val text: LiveData<String> = _text
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/abe/ui/form_transaction/FormTransaction.kt b/app/src/main/java/com/example/abe/ui/form_transaction/FormTransaction.kt
index c7e3ae5f92093e3a48a318dee8cd78b0fbe52f8a..96588b63e6f5f7d8acba26b0aad4f015e504d347 100644
--- a/app/src/main/java/com/example/abe/ui/form_transaction/FormTransaction.kt
+++ b/app/src/main/java/com/example/abe/ui/form_transaction/FormTransaction.kt
@@ -46,7 +46,7 @@ class FormTransaction : Fragment() {
 
 
     private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
-    private val permissionId = 5
+    private var isNewTrx = true
 
     private val viewModel: FormTransactionViewModel by viewModels {
         FormTransactionViewModelFactory((activity?.application as ABEApplication).repository)
@@ -107,6 +107,7 @@ class FormTransaction : Fragment() {
             if (args.containsKey("idx-id")) {
                 val trxId = args.getInt("idx-id")
                 displayTrx(trxId)
+                isNewTrx = false
             } else if (args.containsKey("random_amount")) {
                 viewModel.setRandomAmount(args.getInt("random_amount"))
                 useNewTrxLayout()
@@ -271,7 +272,7 @@ class FormTransaction : Fragment() {
         }
         if (granted) {
             getCurrentLocation()
-        } else {
+        } else if (isNewTrx) {
             val defaultLatitude = -6.892382
             val defaultLongitude = 107.608352
             Toast.makeText(requireActivity(), "Location set to default", Toast.LENGTH_SHORT).show()
@@ -307,13 +308,13 @@ class FormTransaction : Fragment() {
                 val location: Location? = task.result
                 if (location != null) {
                     setLocation(location.latitude, location.longitude)
-                } else {
+                } else if (isNewTrx) {
                     Toast.makeText(requireActivity(), "Location set to default", Toast.LENGTH_SHORT)
                         .show()
                     setLocation(defaultLatitude, defaultLongitude)
                 }
             }
-        } else {
+        } else if (isNewTrx) {
             Toast.makeText(requireActivity(), "Location set to default", Toast.LENGTH_SHORT).show()
             setLocation(defaultLatitude, defaultLongitude)
         }
diff --git a/app/src/main/java/com/example/abe/ui/home/HomeFragment.kt b/app/src/main/java/com/example/abe/ui/home/HomeFragment.kt
deleted file mode 100644
index bfa9c99f55e6d26ea3b044bea9ede6af92bf3be8..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/example/abe/ui/home/HomeFragment.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.example.abe.ui.home
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import com.example.abe.databinding.FragmentHomeBinding
-
-class HomeFragment : Fragment() {
-
-    private var _binding: FragmentHomeBinding? = null
-
-    // This property is only valid between onCreateView and
-    // onDestroyView.
-    private val binding get() = _binding!!
-
-    override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
-    ): View {
-        val homeViewModel =
-                ViewModelProvider(this).get(HomeViewModel::class.java)
-
-        _binding = FragmentHomeBinding.inflate(inflater, container, false)
-        val root: View = binding.root
-
-        val textView: TextView = binding.textHome
-        homeViewModel.text.observe(viewLifecycleOwner) {
-            textView.text = it
-        }
-        return root
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        _binding = null
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/abe/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/abe/ui/home/HomeViewModel.kt
deleted file mode 100644
index c17bcd97d032b49640948b6b249adcfbdc713c86..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/example/abe/ui/home/HomeViewModel.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.example.abe.ui.home
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-
-class HomeViewModel : ViewModel() {
-
-    private val _text = MutableLiveData<String>().apply {
-        value = "This is home Fragment"
-    }
-    val text: LiveData<String> = _text
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/abe/ui/login/LoginActivity.kt b/app/src/main/java/com/example/abe/ui/login/LoginActivity.kt
index b562f9192cfc49650f088827b0d7610756297e22..e8869b740bac22c18b48d1915e2bfe56fe0819c0 100644
--- a/app/src/main/java/com/example/abe/ui/login/LoginActivity.kt
+++ b/app/src/main/java/com/example/abe/ui/login/LoginActivity.kt
@@ -1,6 +1,7 @@
 package com.example.abe.ui.login
 
 import android.content.Intent
+import android.content.pm.ActivityInfo
 import android.os.Bundle
 import android.util.Patterns
 import android.view.View
@@ -65,6 +66,7 @@ class LoginActivity : AppCompatActivity(), LoginResultCallback {
         super.onCreate(savedInstanceState)
         binding = ActivityLoginBinding.inflate(layoutInflater)
 
+        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
         supportActionBar?.hide()
 
         val view = binding.root
diff --git a/app/src/main/java/com/example/abe/ui/scanner/ScannerFragment.kt b/app/src/main/java/com/example/abe/ui/scanner/ScannerFragment.kt
index c3a4f37f34228a5bfeee7ee3a018c6f78a7bacdb..7988086650124b35ef44585061d22ff331e78b83 100644
--- a/app/src/main/java/com/example/abe/ui/scanner/ScannerFragment.kt
+++ b/app/src/main/java/com/example/abe/ui/scanner/ScannerFragment.kt
@@ -64,13 +64,14 @@ class ScannerFragment : Fragment(), UploadResultCallback {
     private lateinit var fusedLocationClient: FusedLocationProviderClient
     private var latitude = 0.0
     private var longitude = 0.0
-    private var useDefaultLocation = false
 
     private val DEFAULT_LATITUDE = -6.892382
     private val DEFAULT_LONGINTUDE = 107.608352
 
     private lateinit var user: String
 
+    private var isProcessingPhoto = false
+
     private val viewModel: ScannerViewModel by viewModels {
         ScannerViewModelFactory((activity?.application as ABEApplication).repository)
     }
@@ -118,6 +119,7 @@ class ScannerFragment : Fragment(), UploadResultCallback {
                 } else {
                     val msg = "Failed to fetch image from gallery"
                     Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
+                    isProcessingPhoto = false
                 }
             }
         }
@@ -137,12 +139,21 @@ class ScannerFragment : Fragment(), UploadResultCallback {
         }
 
         lifecycleScope.launch {
-            user =  (activity as MainActivity).preferenceDataStoreHelper.getFirstPreference(
-                PreferenceDataStoreConstants.USER,"")
+            user = (activity as MainActivity).preferenceDataStoreHelper.getFirstPreference(
+                PreferenceDataStoreConstants.USER, ""
+            )
         }
 
         binding.captureButton.setOnClickListener {
-            takePicture()
+            if (cameraPermissionGranted()) {
+                takePicture()
+            } else {
+                Toast.makeText(
+                    requireContext(),
+                    "Please allow camera to take photos",
+                    Toast.LENGTH_SHORT
+                ).show()
+            }
         }
 
         binding.galleryPreviewButton.setOnClickListener {
@@ -190,13 +201,17 @@ class ScannerFragment : Fragment(), UploadResultCallback {
     private fun attemptUpload(imageFile: File) {
         lifecycleScope.launch {
             val retrofit = Retrofit()
-            val token = (activity as MainActivity).preferenceDataStoreHelper.getFirstPreference(PreferenceDataStoreConstants.TOKEN, "")
+            val token = (activity as MainActivity).preferenceDataStoreHelper.getFirstPreference(
+                PreferenceDataStoreConstants.TOKEN,
+                ""
+            )
             retrofit.upload(token, imageFile, this@ScannerFragment)
         }
     }
 
     private fun showPreviewDialog(imageUri: Uri) {
         val dialog = Dialog(requireContext()).apply {
+            setCancelable(false)
             setContentView(R.layout.dialog_image_preview)
             window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
         }
@@ -212,23 +227,25 @@ class ScannerFragment : Fragment(), UploadResultCallback {
 
         confirmButton.setOnClickListener {
             val activity = activity as MainActivity
-            if(!isConnected(activity.getNetworkState())) {
+            if (!isConnected(activity.getNetworkState())) {
                 dialog.dismiss()
                 binding.scanLayout.visibility = View.GONE
                 binding.noNetworkLayout.visibility = View.VISIBLE
+                isProcessingPhoto = false
             } else {
-            val filePath = imageUri.path
-            if (filePath != null) {
-                val imageFile = File(filePath)
-                attemptUpload(imageFile)
+                val filePath = imageUri.path
+                if (filePath != null) {
+                    val imageFile = File(filePath)
+                    attemptUpload(imageFile)
 
-                val msg = "Uploading photo, please wait"
-                Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
+                    val msg = "Uploading photo, please wait"
+                    Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
 
-                dialog.dismiss()
-            } else {
-                dialog.dismiss()
-            }
+                    dialog.dismiss()
+                } else {
+                    isProcessingPhoto = false
+                    dialog.dismiss()
+                }
             }
         }
 
@@ -239,12 +256,22 @@ class ScannerFragment : Fragment(), UploadResultCallback {
 
         cancelButton.setOnClickListener {
             dialog.dismiss()
+            isProcessingPhoto = false
         }
 
         dialog.show()
     }
 
     private fun takePicture() {
+        if (isProcessingPhoto) {
+            Toast.makeText(
+                requireContext(),
+                "Unable to take picture, processing previous image",
+                Toast.LENGTH_SHORT
+            ).show()
+            return
+        }
+
         val imageCapture = imageCapture
 
         val photoFile = File(
@@ -254,6 +281,7 @@ class ScannerFragment : Fragment(), UploadResultCallback {
 
         val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
 
+        isProcessingPhoto = true
         imageCapture.takePicture(
             outputOptions,
             ContextCompat.getMainExecutor(requireContext()),
@@ -261,6 +289,7 @@ class ScannerFragment : Fragment(), UploadResultCallback {
                 override fun onError(exc: ImageCaptureException) {
                     Toast.makeText(requireContext(), "Photo capture failed", Toast.LENGTH_SHORT)
                         .show()
+                    isProcessingPhoto = false
                 }
 
                 override fun onImageSaved(output: ImageCapture.OutputFileResults) {
@@ -274,7 +303,6 @@ class ScannerFragment : Fragment(), UploadResultCallback {
     private fun setLocationAsDefault() {
         latitude = DEFAULT_LATITUDE
         longitude = DEFAULT_LONGINTUDE
-        useDefaultLocation = true
     }
 
     @SuppressLint("MissingPermission")
@@ -295,6 +323,7 @@ class ScannerFragment : Fragment(), UploadResultCallback {
                 }
         } else {
             setLocationAsDefault()
+            insertItems()
         }
     }
 
@@ -351,7 +380,7 @@ class ScannerFragment : Fragment(), UploadResultCallback {
         val locationList: MutableList<Address> =
             geocoder.getFromLocation(latitude, longitude, 1) ?: mutableListOf<Address>()
         val location =
-            if (!useDefaultLocation && locationList.size > 0) (locationList[0].getAddressLine(0)) else "Unknown location"
+            if (locationList.size > 0) (locationList[0].getAddressLine(0)) else "Unknown location"
 
         uploadResponse?.items?.items?.forEach { item ->
             viewModel.insertTransaction(user, item, latitude, longitude, location)
@@ -361,16 +390,28 @@ class ScannerFragment : Fragment(), UploadResultCallback {
 
         findNavController().navigate(R.id.action_navigation_scanner_to_navigation_transactions)
         uploadResponse = null
+        isProcessingPhoto = false
     }
 
     override fun onFailure(errorMessage: String) {
         Toast.makeText(requireContext(), "Upload failed", Toast.LENGTH_SHORT).show()
         Log.e("ABE-PHO", errorMessage)
+        isProcessingPhoto = false
     }
 
     private fun openGallery() {
+        if (isProcessingPhoto) {
+            Toast.makeText(
+                requireContext(),
+                "Unable choose image, processing previous image",
+                Toast.LENGTH_SHORT
+            ).show()
+            return
+        }
+
         val intent = Intent(Intent.ACTION_PICK)
         intent.type = "image/*"
+        isProcessingPhoto = true
         openGalleryLauncher.launch(intent)
     }
 
diff --git a/app/src/main/java/com/example/abe/ui/twibbon/TwibbonFragment.kt b/app/src/main/java/com/example/abe/ui/twibbon/TwibbonFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ffc798f88d346c698bf7e8ee551b7f6dd55dd0e4
--- /dev/null
+++ b/app/src/main/java/com/example/abe/ui/twibbon/TwibbonFragment.kt
@@ -0,0 +1,316 @@
+package com.example.abe.ui.twibbon
+
+import android.Manifest
+import android.app.Dialog
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.Bitmap.CompressFormat
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.drawable.ColorDrawable
+import android.net.Uri
+import android.os.Bundle
+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.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.Preview
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.core.content.ContextCompat
+import androidx.exifinterface.media.ExifInterface
+import androidx.fragment.app.Fragment
+import com.bumptech.glide.Glide
+import com.example.abe.R
+import com.example.abe.databinding.FragmentTwibbonBinding
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.net.URI
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.ExecutorService
+
+
+class TwibbonFragment : Fragment() {
+    private var _binding: FragmentTwibbonBinding? = null
+    private val binding get() = _binding!!
+
+    private var imageCapture: ImageCapture? = null
+
+    private var isProcessingPhoto = false
+
+    companion object {
+        private const val TAG = "ABE-TWB"
+        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        _binding = FragmentTwibbonBinding.inflate(inflater, container, false)
+
+        if (cameraPermissionsGranted()) {
+            startCamera()
+        } else {
+            requestCameraPermissions()
+        }
+
+        binding.btnCaptureTwibbon.setOnClickListener {
+            if (cameraPermissionsGranted()) {
+                previewTwibbon()
+            } else {
+                Toast.makeText(requireContext(), "Please allow camera to take photos", Toast.LENGTH_SHORT).show()
+            }
+        }
+
+        return binding.root
+    }
+
+    private fun startCamera() {
+        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
+
+        cameraProviderFuture.addListener({
+            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
+
+            val preview = Preview.Builder()
+                .build()
+                .also {
+                    it.setSurfaceProvider(binding.prvTwibbon.surfaceProvider)
+                }
+            imageCapture = ImageCapture.Builder().build()
+
+            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
+
+            try {
+                cameraProvider.unbindAll()
+
+                cameraProvider.bindToLifecycle(
+                    this, cameraSelector, preview, imageCapture
+                )
+
+            } catch (exc: Exception) {
+                Log.e(TAG, "Use case binding failed", exc)
+            }
+
+        }, ContextCompat.getMainExecutor(requireContext()))
+    }
+
+    private val cameraPermissionsLauncher =
+        registerForActivityResult(
+            ActivityResultContracts.RequestPermission()
+        )
+        { isGranted ->
+            if (isGranted) {
+                startCamera()
+            } else {
+                Toast.makeText(
+                    requireContext(),
+                    "Please allow camera to use Twibbon",
+                    Toast.LENGTH_SHORT
+                ).show()
+            }
+        }
+
+    private fun previewTwibbon() {
+        if (isProcessingPhoto) {
+            Toast.makeText(requireContext(), "Unable to take picture, processing previous image", Toast.LENGTH_SHORT).show()
+            return
+        }
+
+        isProcessingPhoto = true
+        Toast.makeText(requireContext(), "Generating twibbon", Toast.LENGTH_SHORT).show()
+        val imageCapture = imageCapture ?: return
+        deletePreviousTwibbons()
+
+        val photoFile = File(
+            requireContext().cacheDir,
+            "twibbon_${
+                SimpleDateFormat(
+                    FILENAME_FORMAT,
+                    Locale.US
+                ).format(System.currentTimeMillis())
+            }.jpg"
+        )
+
+        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
+
+        imageCapture.takePicture(
+            outputOptions,
+            ContextCompat.getMainExecutor(requireContext()),
+            object : ImageCapture.OnImageSavedCallback {
+                override fun onError(exc: ImageCaptureException) {
+                    Toast.makeText(requireContext(), "Photo capture failed", Toast.LENGTH_SHORT)
+                        .show()
+                    isProcessingPhoto = false
+                }
+
+                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
+                    val savedUri = Uri.fromFile(photoFile)
+                    overlayTwibbonToImage(savedUri)
+                    showPreviewDialog(savedUri)
+                }
+            }
+        )
+    }
+
+    private fun overlayTwibbonToImage(imageUri: Uri) {
+        try {
+            val exifIms = requireActivity().contentResolver.openInputStream(imageUri) ?: return
+            val exif = ExifInterface(exifIms)
+            val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
+
+            val overlayBitmap = BitmapFactory.decodeResource(
+                requireContext().resources,
+                R.drawable.img_default_twibbon
+            )
+
+            val bitmapIms = requireActivity().contentResolver.openInputStream(imageUri) ?: return
+            val originalPhotoBitmap = BitmapFactory.decodeStream(bitmapIms)
+            val rotationMatrix = Matrix()
+
+            when (orientation) {
+                6 -> {
+                    rotationMatrix.postRotate(90f)
+                }
+
+                3 -> {
+                    rotationMatrix.postRotate(180f)
+                }
+
+                8 -> {
+                    rotationMatrix.postRotate(270f)
+                }
+            }
+
+            val rotatedBitmap = Bitmap.createBitmap(
+                originalPhotoBitmap,
+                0,
+                0,
+                originalPhotoBitmap.width,
+                originalPhotoBitmap.height,
+                rotationMatrix,
+                true
+            )
+
+            val flipMatrix = Matrix().apply {
+                postScale(
+                    -1f,
+                    1f,
+                    rotatedBitmap.width.toFloat() / 2,
+                    rotatedBitmap.height.toFloat()
+                )
+            }
+            val flippedBitmap = Bitmap.createBitmap(
+                rotatedBitmap,
+                0,
+                0,
+                rotatedBitmap.width,
+                rotatedBitmap.height,
+                flipMatrix,
+                true
+            )
+
+            val scaleMatrix = Matrix().apply {
+                val scale = overlayBitmap.width.toFloat() / flippedBitmap.width.toFloat()
+                postScale(
+                    scale,
+                    scale,
+                    flippedBitmap.width.toFloat(),
+                    flippedBitmap.height.toFloat()
+                )
+            }
+
+            val photoBitmap = Bitmap.createBitmap(
+                flippedBitmap,
+                0,
+                0,
+                flippedBitmap.width,
+                flippedBitmap.height,
+                scaleMatrix,
+                true
+            )
+            val translateMatrix = Matrix().apply {
+                postTranslate(
+                    0f,
+                    (overlayBitmap.height.toFloat() - photoBitmap.height.toFloat()) / 2f
+                )
+            }
+
+            val resultBitmap =
+                Bitmap.createBitmap(
+                    overlayBitmap.width,
+                    overlayBitmap.height,
+                    overlayBitmap.getConfig()
+                )
+            val canvas = Canvas(resultBitmap)
+            canvas.drawBitmap(photoBitmap, translateMatrix, null)
+            canvas.drawBitmap(overlayBitmap, Matrix(), null)
+
+            val bos = ByteArrayOutputStream()
+            resultBitmap.compress(CompressFormat.JPEG, 100, bos)
+            val bitmapData = bos.toByteArray()
+
+            val f = File(URI(imageUri.toString()))
+
+            val fos = FileOutputStream(f)
+            fos.write(bitmapData)
+            fos.flush()
+            fos.close()
+
+        } catch (e: FileNotFoundException) {
+            e.printStackTrace()
+        }
+    }
+
+    private fun deletePreviousTwibbons() {
+        requireContext().cacheDir?.apply {
+            val files = listFiles() ?: emptyArray()
+            files.forEach { file ->
+                if (file.name.startsWith("twibbon"))
+                    file.delete()
+            }
+        }
+    }
+
+    private fun showPreviewDialog(imageUri: Uri) {
+        val dialog = Dialog(requireContext()).apply {
+            setCancelable(false)
+            setContentView(R.layout.dialog_twibbon_preview)
+            window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        }
+
+        val imageView = dialog.findViewById<ImageView>(R.id.ivTwibbonPreview)
+
+        Glide.with(requireContext())
+            .load(imageUri)
+            .into(imageView)
+
+        val closeButton = dialog.findViewById<Button>(R.id.btnCloseTwibbon)
+
+        closeButton.setOnClickListener {
+            isProcessingPhoto = false
+            dialog.dismiss()
+        }
+
+        dialog.show()
+    }
+
+    private fun cameraPermissionsGranted() = ContextCompat.checkSelfPermission(
+        requireContext(), Manifest.permission.CAMERA
+    ) == PackageManager.PERMISSION_GRANTED
+
+    private fun requestCameraPermissions() {
+        cameraPermissionsLauncher.launch(Manifest.permission.CAMERA)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_twibbon.xml b/app/src/main/res/drawable/ic_twibbon.xml
new file mode 100644
index 0000000000000000000000000000000000000000..00c4042dc5a00b86dd4de79e9fe130e45c6698f3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_twibbon.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+  <path
+      android:fillColor="@color/primary"
+      android:pathData="M520,840L200,840q-33,0 -56.5,-23.5T120,760v-320h400v400ZM160,760h320L376,620l-76,100 -56,-74 -84,114ZM200,200h-80q0,-33 23.5,-56.5T200,120v80ZM280,200v-80h80v80h-80ZM440,200v-80h80v80h-80ZM600,200v-80h80v80h-80ZM600,840v-80h80v80h-80ZM760,200v-80q33,0 56.5,23.5T840,200h-80ZM120,360v-80h80v80h-80ZM760,760h80q0,33 -23.5,56.5T760,840v-80ZM760,680v-80h80v80h-80ZM760,520v-80h80v80h-80ZM760,360v-80h80v80h-80Z"/>
+</vector>
diff --git a/app/src/main/res/drawable/img_default_twibbon.png b/app/src/main/res/drawable/img_default_twibbon.png
new file mode 100644
index 0000000000000000000000000000000000000000..59df76b7525c37668922be2df1fcf8606a543ade
Binary files /dev/null and b/app/src/main/res/drawable/img_default_twibbon.png differ
diff --git a/app/src/main/res/layout/dialog_twibbon_preview.xml b/app/src/main/res/layout/dialog_twibbon_preview.xml
new file mode 100644
index 0000000000000000000000000000000000000000..16fe1878211715773e9851e30520d1eea9668667
--- /dev/null
+++ b/app/src/main/res/layout/dialog_twibbon_preview.xml
@@ -0,0 +1,32 @@
+<?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">
+
+    <ImageView
+        android:id="@+id/ivTwibbonPreview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.5"
+        tools:srcCompat="@tools:sample/avatars" />
+
+    <Button
+        android:id="@+id/btnCloseTwibbon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="48dp"
+        android:text="Close Preview"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintHorizontal_chainStyle="spread"
+        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
deleted file mode 100644
index 166ab0e9e603c1f230a7b9514d293b963ab2309e..0000000000000000000000000000000000000000
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ /dev/null
@@ -1,22 +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=".ui.dashboard.DashboardFragment">
-
-    <TextView
-        android:id="@+id/text_dashboard"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="8dp"
-        android:textAlignment="center"
-        android:textSize="20sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
deleted file mode 100644
index f3d9b08ffe6101e25c77c5fae7e28bb5dfa11fbd..0000000000000000000000000000000000000000
--- a/app/src/main/res/layout/fragment_home.xml
+++ /dev/null
@@ -1,22 +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=".ui.home.HomeFragment">
-
-    <TextView
-        android:id="@+id/text_home"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="8dp"
-        android:textAlignment="center"
-        android:textSize="20sp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
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..acddcc0ba9259ca25033bb9061177eb4c30d82ae
--- /dev/null
+++ b/app/src/main/res/layout/fragment_twibbon.xml
@@ -0,0 +1,72 @@
+<?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">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:background="@color/grayLight"
+        app:layout_constraintBottom_toTopOf="@+id/clPreviewBottomBar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.5"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.camera.view.PreviewView
+            android:id="@+id/prvTwibbon"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed" />
+
+        <ImageView
+            android:id="@+id/ivTwibbonOverlay"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:src="@drawable/img_default_twibbon"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintDimensionRatio="1:1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0.5"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.5"
+            android:contentDescription="@string/twibbon_overlay" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/clPreviewBottomBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/primaryActive"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent">
+
+        <Button
+            android:id="@+id/btnCaptureTwibbon"
+            android:layout_width="80dp"
+            android:layout_height="80dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            android:background="@drawable/scan_button"
+            android:backgroundTint="#FFFFFF"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</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 8f9ae85695d9e5c3c51856393458b5ab6a780966..3d0ae195ab8db9a35331632b3b0012dccadf4f32 100644
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -6,11 +6,18 @@
         android:icon="@drawable/ic_transactions"
         android:title="@string/transaction" />
 
+    <item
+        android:id="@+id/navigation_twibbon"
+        android:icon="@drawable/ic_twibbon"
+        android:title="Twibbon"
+        />
+
     <item
         android:id="@+id/navigation_scan"
         android:icon="@drawable/ic_scan"
         android:title="@string/scan" />
 
+
     <item
         android:id="@+id/navigation_graph"
         android:icon="@drawable/ic_graph"
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index 73076dc2852be4ecf20540e7b04b98d2bb56190f..89e74cf9cc6ead417575003857e1fe66867382fe 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -57,6 +57,13 @@
             app:destination="@id/navigation_transactions" />
     </fragment>
 
+    <fragment
+        android:id="@+id/navigation_twibbon"
+        android:label="Twibbon"
+        android:name="com.example.abe.ui.twibbon.TwibbonFragment"
+        tools:layout="@layout/fragment_twibbon"
+        />
+
 <!--    Example when fragment & layout is done-->
 <!--    <fragment-->
 <!--        android:id="@+id/navigation_graph"-->
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1f2ba06c6c06e63b342a2171d519053856eb686b..b6150e23f188544f100326d84e7e6f2c006be681 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -30,4 +30,7 @@
         <item>Income</item>
         <item>Expenses</item>
     </string-array>
+    <!-- TODO: Remove or change this placeholder text -->
+    <string name="hello_blank_fragment">Hello blank fragment</string>
+    <string name="twibbon_overlay">Twibbon overlay</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/xml/filepaths.xml b/app/src/main/res/xml/filepaths.xml
index 405a83d4ee28acc51f581b1c950679f7da123423..fe2c7dc9388c4f983dc7125dff5b589bc07a1131 100644
--- a/app/src/main/res/xml/filepaths.xml
+++ b/app/src/main/res/xml/filepaths.xml
@@ -1,10 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <paths>
-    <external-cache-path
+    <cache-path
         name="exports"
         path="/" />
-
-<!--    <cache-path-->
-<!--        name="exports"-->
-<!--        path="/" />-->
 </paths>
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e5e09b1293522859c5ffd5fe710e064d1f791d1a..df9a53d24b214f92f2ee16a5dc3ea323b94d9b54 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -27,6 +27,7 @@ playServicesLocation = "21.2.0"
 camerax = "1.4.0-alpha04"
 glide = "4.12.0"
 okhttp = "4.9.0"
+exifinterface = "1.3.7"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -59,6 +60,7 @@ glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
 glideCompiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
 okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
 logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
+androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exifinterface" }
 
 [plugins]
 androidApplication = { id = "com.android.application", version.ref = "agp" }