diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2df39f1240544c6d3460e77341b980cf2048d9c4..0089e4fbb12a612f9ec58933fead21efc8ec8fdd 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,7 @@
 plugins {
     id("com.android.application")
     id("org.jetbrains.kotlin.android")
+    id("com.google.devtools.ksp")
 }
 
 android {
@@ -33,22 +34,59 @@ android {
     buildFeatures {
         viewBinding = true
     }
+
+    packaging {
+        resources.excludes.add("META-INF/DEPENDENCIES")
+    }
 }
 
+
+
 dependencies {
+    implementation("com.google.android.gms:play-services-location:21.2.0")
+    val roomVersion = "2.6.1"
 
     implementation("androidx.core:core-ktx:1.8.0")
     implementation("androidx.appcompat:appcompat:1.5.1")
     implementation("com.google.android.material:material:1.9.0")
     implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+
     implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
     implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
+
     implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
     implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
+
     implementation("androidx.legacy:legacy-support-v4:1.0.0")
+
     implementation("androidx.fragment:fragment-ktx:1.5.7")
     implementation("androidx.annotation:annotation:1.6.0")
+
+    implementation("com.squareup.retrofit2:retrofit:2.9.0")
+
+    implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
+    implementation("com.squareup.okhttp3:okhttp")
+    implementation("com.squareup.okhttp3:logging-interceptor")
+
+    implementation("com.squareup.moshi:moshi:1.15.0")
+    implementation("com.squareup.moshi:moshi-kotlin:1.15.0")
+    implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
+    implementation("androidx.preference:preference:1.2.1")
+
+    implementation("io.github.evanrupert:excelkt:1.0.2")
+
+    implementation("androidx.room:room-ktx:$roomVersion")
+    ksp("androidx.room:room-compiler:$roomVersion")
+
     testImplementation("junit:junit:4.13.2")
     androidTestImplementation("androidx.test.ext:junit:1.1.5")
     androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+
+    implementation("androidx.camera:camera-camera2:1.3.2")
+    implementation("androidx.camera:camera-view:1.3.2")
+    implementation("androidx.camera:camera-lifecycle:1.3.2")
+
+    implementation("androidx.activity:activity-ktx:1.8.2")
+
+
 }
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e55f0a7a984a0cbf707958ae60b2b92f7793b918..55fee1665cff2f3ad7c1128bbb5fe531a776a3a2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,10 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
-    
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+    <uses-feature
+        android:name="android.hardware.camera"
+        android:required="false" />
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application
+        android:name=".ui.transactions.TransactionsApplication"
         android:allowBackup="true"
         android:dataExtractionRules="@xml/data_extraction_rules"
         android:fullBackupContent="@xml/backup_rules"
@@ -15,16 +29,47 @@
         android:theme="@style/Theme.BondoYap"
         tools:targetApi="31">
 
+        <service
+            android:name=".service.jwt.JwtService"
+            android:enabled="true"
+            android:exported="true"
+            android:permission="android.permission.INTERNET"></service>
+
+        <receiver
+            android:name=".ui.transactions.TransactionsBroadcastReceiver"
+            android:enabled="true"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.BondoYap.transactions.randomize" />
+            </intent-filter>
+        </receiver>
+
         <activity
-            android:name=".MainActivity"
+            android:name=".ui.login.LoginActivity"
             android:exported="true"
-            android:label="@string/app_name">
+            android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"></activity>
+
     </application>
 
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="geo" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="https" />
+        </intent>
+    </queries>
+
 </manifest>
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/MainActivity.kt b/app/src/main/java/com/example/bondoyap/MainActivity.kt
index 2e8d94000a9261abe7a5da083f5c0373dda0efb5..f02eacef559d1993ef2fcfc82c897ae99dd3fb41 100644
--- a/app/src/main/java/com/example/bondoyap/MainActivity.kt
+++ b/app/src/main/java/com/example/bondoyap/MainActivity.kt
@@ -1,29 +1,46 @@
 package com.example.bondoyap
 
 import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
 import android.content.SharedPreferences
 import android.os.Bundle
 import android.widget.Toast
+import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
 import androidx.navigation.findNavController
 import androidx.navigation.ui.AppBarConfiguration
 import androidx.navigation.ui.setupActionBarWithNavController
 import androidx.navigation.ui.setupWithNavController
 import com.example.bondoyap.databinding.ActivityMainBinding
+import com.example.bondoyap.service.api.Constants
+import com.example.bondoyap.service.api.Constants.SHARED_PREFS_NAME
+import com.example.bondoyap.service.jwt.JwtService
+import com.example.bondoyap.ui.login.LoginActivity
+import com.example.bondoyap.ui.transactions.TransactionsApplication
+import com.example.bondoyap.ui.transactions.TransactionsBroadcastReceiver
+import com.example.bondoyap.ui.transactions.TransactionsViewModel
+import com.example.bondoyap.ui.transactions.TransactionsViewModelFactory
 import com.example.bondoyap.util.network.NetworkObserver
 import com.google.android.material.bottomnavigation.BottomNavigationView
 
-
 class MainActivity : AppCompatActivity() {
 
     private lateinit var binding: ActivityMainBinding
     private lateinit var sharedPreferences: SharedPreferences
     private lateinit var networkObserver: NetworkObserver
+    private lateinit var transactionsBroadcastReceiver: TransactionsBroadcastReceiver
 
+    private val transactionsViewModel: TransactionsViewModel by viewModels {
+        TransactionsViewModelFactory((applicationContext as TransactionsApplication).repository)
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
+
         networkObserver = NetworkObserver(applicationContext)
         networkObserver.isConnected.observe(this) { isConnected ->
             if (isConnected) {
@@ -33,6 +50,15 @@ class MainActivity : AppCompatActivity() {
             }
         }
 
+        if (!isLoggedIn()) {
+            val intent = Intent(this, LoginActivity::class.java)
+            startActivity(intent)
+            finish()
+        }
+
+        val serviceIntent = Intent(this, JwtService::class.java)
+        this.startService(serviceIntent)
+
         binding = ActivityMainBinding.inflate(layoutInflater)
         setContentView(binding.root)
 
@@ -41,16 +67,10 @@ class MainActivity : AppCompatActivity() {
 
         val navController = findNavController(R.id.nav_host_fragment_activity_main)
 
-        sharedPreferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
-
-        if (!isLoggedIn()) {
-            navController.navigate(R.id.navigation_login)
-        }
-
         val appBarConfiguration = AppBarConfiguration(
             setOf(
                 R.id.navigation_settings, R.id.navigation_transactions, R.id.navigation_scanner,
-                R.id.navigation_graph, R.id.navigation_login
+                R.id.navigation_graph
             )
         )
 
@@ -58,18 +78,23 @@ class MainActivity : AppCompatActivity() {
 
         navView.setupWithNavController(navController)
 
-        navController.addOnDestinationChangedListener { _, destination, _ ->
-            // Disable bottom navigation when on the login screen
-            if (destination.id == R.id.navigation_login) {
-                navView.visibility = BottomNavigationView.GONE
-            } else {
-                navView.visibility = BottomNavigationView.VISIBLE
-            }
-        }
+        transactionsBroadcastReceiver = TransactionsBroadcastReceiver(transactionsViewModel)
+        val filter = IntentFilter(Constants.ACTION_RANDOMIZE_TRANSACTIONS)
+        LocalBroadcastManager.getInstance(this)
+            .registerReceiver(transactionsBroadcastReceiver, filter)
+    }
 
+    override fun onSupportNavigateUp(): Boolean {
+        val navController = findNavController(R.id.nav_host_fragment_activity_main)
+        return navController.navigateUp() || super.onSupportNavigateUp()
     }
 
     private fun isLoggedIn(): Boolean {
         return sharedPreferences.getBoolean("isLoggedIn", false)
     }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(transactionsBroadcastReceiver)
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/LocationManager.kt b/app/src/main/java/com/example/bondoyap/service/LocationManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3196f9aa9dbadf66b1e00ea7082b75c1a8e45cbf
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/LocationManager.kt
@@ -0,0 +1,61 @@
+package com.example.bondoyap.service
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.Location
+import android.util.Log
+import androidx.core.app.ActivityCompat
+import androidx.fragment.app.FragmentActivity
+import com.google.android.gms.location.LocationServices
+
+class LocationManager {
+    companion object {
+        fun haveLocationPermission(context: Context): Boolean {
+            return (ActivityCompat.checkSelfPermission(
+                context, android.Manifest.permission.ACCESS_FINE_LOCATION
+            ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
+                context, android.Manifest.permission.ACCESS_COARSE_LOCATION
+            ) == PackageManager.PERMISSION_GRANTED)
+        }
+
+        fun askLocationPermission(context: Context, activity: FragmentActivity) {
+            if (!haveLocationPermission(context)) {
+                ActivityCompat.requestPermissions(
+                    activity, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 100
+                )
+            }
+        }
+
+        @SuppressLint("MissingPermission")
+        fun getLocation(context: Context): Location? {
+            var location: Location? = null
+            if (haveLocationPermission(context)) {
+                val fusedLocationProviderClient =
+                    LocationServices.getFusedLocationProviderClient(context)
+                Log.d("LocationManager", "Getting last location")
+                @SuppressLint("MissingPermission")
+                val locationProvider = fusedLocationProviderClient.lastLocation
+                locationProvider.addOnSuccessListener {
+                    if (it != null) {
+                        Log.d(
+                            "LocationManager",
+                            "Updating location to latitude: ${it.latitude} and longitude: ${it.longitude}"
+                        )
+                        location = it
+                    }
+                }
+            }
+
+            if (location == null) {
+                Log.d("LocationManager", "Location is null")
+            } else {
+                Log.d(
+                    "LocationManager",
+                    "Get location at latitude: ${location?.latitude} and longitude: ${location?.longitude}"
+                )
+            }
+            return location
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/SessionManager.kt b/app/src/main/java/com/example/bondoyap/service/SessionManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a58fbedd688c66addcce7c0d7e46097fd22abd4
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/SessionManager.kt
@@ -0,0 +1,52 @@
+package com.example.bondoyap.service
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.example.bondoyap.service.api.Constants.SHARED_PREFS_NAME
+import com.example.bondoyap.ui.login.data.model.LoggedInUser
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+
+class SessionManager(context: Context) {
+
+    private var prefs: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_NAME,
+        Context.MODE_PRIVATE)
+    private val editor: SharedPreferences.Editor = prefs.edit()
+
+
+    private val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
+    private val userAdapter: JsonAdapter<LoggedInUser> = moshi.adapter(LoggedInUser::class.java)
+
+    private fun getUser(): LoggedInUser? {
+        val userJson = prefs.getString("loggedInUser", null)
+        return userJson?.let {
+            userAdapter.fromJson(it)
+        }
+    }
+
+    fun saveExp(exp: Long){
+        editor.putLong("exp", exp)
+        editor.apply()
+    }
+
+    fun hasExp(): Boolean{
+        return getExp().toInt() != -1
+    }
+
+    fun getExp(): Long {
+        return prefs.getLong("exp", -1)
+    }
+
+    fun getToken(): String? {
+        val user = getUser()
+        return user?.let {
+            user.token
+        }
+    }
+
+    fun logout(){
+        editor.clear()
+        editor.apply()
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/api/ApiClient.kt b/app/src/main/java/com/example/bondoyap/service/api/ApiClient.kt
new file mode 100644
index 0000000000000000000000000000000000000000..02b03eedad67243043d2ec12012358a46c6ca360
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/ApiClient.kt
@@ -0,0 +1,40 @@
+package com.example.bondoyap.service.api
+
+import android.content.Context
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.moshi.MoshiConverterFactory
+
+class ApiClient {
+    private lateinit var apiService: ApiService
+    fun getApiService(context: Context): ApiService {
+
+        if (!::apiService.isInitialized) {
+
+            val moshi = Moshi.Builder()
+                .add(KotlinJsonAdapterFactory())
+                .build()
+
+            val retrofit = Retrofit.Builder()
+                .baseUrl(Constants.BASE_URL)
+                .client(okhttpClient(context))
+                .addConverterFactory(MoshiConverterFactory.create(moshi))
+                .build()
+
+            apiService = retrofit.create(ApiService::class.java)
+        }
+        return apiService
+    }
+
+    private fun okhttpClient(context: Context): OkHttpClient {
+        return OkHttpClient.Builder()
+            .addInterceptor(AuthInterceptor(context))
+            .build()
+    }
+
+}
+
+
+
diff --git a/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt b/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..803015586a080eda094a614661e240d63a0eebe1
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/ApiService.kt
@@ -0,0 +1,30 @@
+package com.example.bondoyap.service.api
+
+import com.example.bondoyap.service.api.data.BillResponse
+import com.example.bondoyap.service.api.data.LoginRequest
+import com.example.bondoyap.service.api.data.LoginResponse
+import com.example.bondoyap.service.api.data.TokenResponse
+import okhttp3.MultipartBody
+import retrofit2.Call
+import retrofit2.http.Body
+import retrofit2.http.Multipart
+import retrofit2.http.POST
+import retrofit2.http.Part
+
+interface ApiService {
+    @POST("auth/login")
+    fun login(
+        @Body loginRequest: LoginRequest
+    ): Call<LoginResponse>
+
+    @POST("auth/token")
+    fun verifyToken(
+        @Body token: String
+    ): Call<TokenResponse>
+
+    @POST("bill/upload")
+    @Multipart
+    fun getBill(
+        @Part photoPart : MultipartBody.Part
+    ) : Call<BillResponse>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/api/AuthInterceptor.kt b/app/src/main/java/com/example/bondoyap/service/api/AuthInterceptor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..62f933c2fb87585dbdccb4095fd668ea970d4d1c
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/AuthInterceptor.kt
@@ -0,0 +1,21 @@
+package com.example.bondoyap.service.api
+
+import android.content.Context
+import com.example.bondoyap.service.SessionManager
+import okhttp3.Interceptor
+import okhttp3.Response
+
+class AuthInterceptor(context: Context) : Interceptor {
+
+    private val sessionManager = SessionManager(context)
+
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val requestBuilder = chain.request().newBuilder()
+
+        sessionManager.getToken()?.let {
+            requestBuilder.addHeader("Authorization", "Bearer $it")
+        }
+
+        return chain.proceed(requestBuilder.build())
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/api/Constants.kt b/app/src/main/java/com/example/bondoyap/service/api/Constants.kt
new file mode 100644
index 0000000000000000000000000000000000000000..897daedea631dae9a33ff6c0eca3671394eb39df
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/Constants.kt
@@ -0,0 +1,7 @@
+package com.example.bondoyap.service.api
+
+object Constants {
+    const val BASE_URL: String = "https://pbd-backend-2024.vercel.app/api/"
+    const val SHARED_PREFS_NAME = "BondoYap"
+    const val ACTION_RANDOMIZE_TRANSACTIONS = "com.BondoYap.transactions.randomize"
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/BillResponse.kt b/app/src/main/java/com/example/bondoyap/service/api/data/BillResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..098c5e658bc2c42e7acb89deea7e3ff7514ea188
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/data/BillResponse.kt
@@ -0,0 +1,5 @@
+package com.example.bondoyap.service.api.data
+
+data class BillResponse(
+    val items : Items
+)
diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/Item.kt b/app/src/main/java/com/example/bondoyap/service/api/data/Item.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cd01390409cf0b82f1638f536e0d814e988817be
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/data/Item.kt
@@ -0,0 +1,9 @@
+package com.example.bondoyap.service.api.data
+import com.squareup.moshi.Json
+
+data class Item(
+    val name : String,
+    @Json(name = "qty")
+    val quantity : Int,
+    val price : Double
+)
diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/Items.kt b/app/src/main/java/com/example/bondoyap/service/api/data/Items.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6c779b17b1baebb2337f106ad94a51398ea47458
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/data/Items.kt
@@ -0,0 +1,6 @@
+package com.example.bondoyap.service.api.data
+import com.squareup.moshi.Json
+
+data class Items(
+    val items : List<Item>
+)
diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/LoginRequest.kt b/app/src/main/java/com/example/bondoyap/service/api/data/LoginRequest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..583c050d0c129816df5e0901d18bdc0a60534c14
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/data/LoginRequest.kt
@@ -0,0 +1,6 @@
+package com.example.bondoyap.service.api.data
+
+data class LoginRequest(
+    val email: String,
+    val password: String
+)
diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/LoginResponse.kt b/app/src/main/java/com/example/bondoyap/service/api/data/LoginResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ca1bb881dc6e0eabc38cb64374fd2eee615d6a52
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/data/LoginResponse.kt
@@ -0,0 +1,5 @@
+package com.example.bondoyap.service.api.data
+
+data class LoginResponse(
+    val token: String
+)
diff --git a/app/src/main/java/com/example/bondoyap/service/api/data/TokenResponse.kt b/app/src/main/java/com/example/bondoyap/service/api/data/TokenResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7f8a2be82dbdbfd2846355c965f5072680a02efb
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/api/data/TokenResponse.kt
@@ -0,0 +1,7 @@
+package com.example.bondoyap.service.api.data
+
+data class TokenResponse(
+    val nim: String,
+    val iat: Long,
+    val exp: Long
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/service/jwt/JwtService.kt b/app/src/main/java/com/example/bondoyap/service/jwt/JwtService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..39bfd27dd4a6dcd60679e300b6ed963d2e34071f
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/service/jwt/JwtService.kt
@@ -0,0 +1,115 @@
+package com.example.bondoyap.service.jwt
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Handler
+import android.os.IBinder
+import android.util.Log
+import android.widget.Toast
+import com.example.bondoyap.service.SessionManager
+import com.example.bondoyap.service.api.ApiClient
+import com.example.bondoyap.service.api.data.TokenResponse
+import com.example.bondoyap.ui.login.LoginActivity
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+
+class JwtService : Service() {
+
+    private lateinit var handler : Handler
+
+    private lateinit var session: SessionManager
+
+    private val task = object : Runnable {
+        override fun run() {
+            Log.d("JwtService", "Current Time: ${System.currentTimeMillis()}")
+
+            session =  SessionManager(applicationContext)
+            val token = session.getToken()
+
+            if (token != null) {
+                verifyJwt(applicationContext, token)
+            }
+            // 10 second
+            handler.postDelayed(this, 10000)
+        }
+    }
+
+    private lateinit var apiClient: ApiClient
+
+    override fun onCreate() {
+        super.onCreate()
+        handler = Handler(mainLooper)
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        handler.post(task)
+        return START_STICKY
+    }
+
+    override fun onBind(intent: Intent): IBinder? {
+        return null
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        handler.removeCallbacks(task)
+    }
+
+    fun verifyJwt(context: Context, token: String){
+        if (session.hasExp()) {
+            checkJwt(session.getExp())
+            return
+        }
+        apiClient = ApiClient()
+        apiClient.getApiService(context).verifyToken(token).enqueue(object: Callback<TokenResponse>{
+            override fun onFailure(call: Call<TokenResponse>, t: Throwable) {
+                Log.d("JwtService", "Error Failure")
+            }
+
+            override fun onResponse(call: Call<TokenResponse>, response: Response<TokenResponse>) {
+                Log.d("JwtService", "Getting Response")
+
+                val tokenResponse = response.body()
+
+                if (tokenResponse != null){
+                    Log.d("JwtService", "Updating Exp")
+
+                    session.saveExp(tokenResponse.exp)
+                    checkJwt(tokenResponse.exp)
+                } else {
+                    Log.d("JwtService", "Error response")
+                    Log.d("JwtService", "Error expired?")
+                    handleExpired()
+                }
+
+            }
+        })
+    }
+
+    fun checkJwt(exp: Long) {
+        val currentTimestamp = System.currentTimeMillis() / 1000
+
+        Log.d("JwtService", "exp : ${exp}")
+        Log.d("JwtService", "currentTime : ${currentTimestamp}")
+
+        if (currentTimestamp >= exp) {
+            Log.d("JwtService", "JWT is expired")
+            handleExpired()
+        } else {
+            Log.d("JwtService", "JWT is still valid")
+        }
+    }
+
+    fun handleExpired(){
+        session.logout()
+        Toast.makeText(applicationContext, "Session Expired! \n Logging out ...", Toast.LENGTH_LONG).show()
+
+        val intent = Intent(applicationContext, LoginActivity::class.java)
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+        startActivity(intent)
+        stopSelf()
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/LoggedInUserView.kt b/app/src/main/java/com/example/bondoyap/ui/login/LoggedInUserView.kt
index 127052153ac667ee17007bf12a87861dfd93308f..106a5999d4bc31f2babf13d5b526b17ff648626e 100644
--- a/app/src/main/java/com/example/bondoyap/ui/login/LoggedInUserView.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/login/LoggedInUserView.kt
@@ -4,6 +4,6 @@ package com.example.bondoyap.ui.login
  * User details post authentication that is exposed to the UI
  */
 data class LoggedInUserView(
-    val displayName: String
+    val email: String
     //... other data fields that may be accessible to the UI
 )
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/LoginActivity.kt b/app/src/main/java/com/example/bondoyap/ui/login/LoginActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e21b23e4fa0c8c171cb0f1e56f67f635e6dae6b2
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/login/LoginActivity.kt
@@ -0,0 +1,165 @@
+package com.example.bondoyap.ui.login
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import androidx.annotation.StringRes
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.widget.EditText
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import com.example.bondoyap.service.api.Constants.SHARED_PREFS_NAME
+import com.example.bondoyap.MainActivity
+import com.example.bondoyap.R
+import com.example.bondoyap.databinding.ActivityLoginBinding
+
+class LoginActivity : AppCompatActivity() {
+
+    private lateinit var loginViewModel: LoginViewModel
+    private lateinit var binding: ActivityLoginBinding
+    private lateinit var sharedPreferences: SharedPreferences
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
+
+
+
+        if (isLoggedIn()) {
+            navigateToMainActivity()
+            finish()
+        } else {
+            binding = ActivityLoginBinding.inflate(layoutInflater)
+            setContentView(binding.root)
+
+            val emailEditText = binding.email
+            val passwordEditText = binding.password
+            val loginButton = binding.login
+            val loadingProgressBar = binding.loading
+
+            val factory = LoginViewModelFactory(this)
+            loginViewModel = ViewModelProvider(this, factory)[LoginViewModel::class.java]
+
+            // skip login
+            loginViewModel.login(
+                "13521xxx@std.stei.itb.ac.id",
+                "password_13521xxx"
+            )
+            if (isLoggedIn()) {
+                navigateToMainActivity()
+                finish()
+            }
+
+            loginViewModel.loginFormState.observe(this@LoginActivity, Observer {
+                val loginState = it ?: return@Observer
+
+                // disable login button unless both username / password is valid
+                loginButton.isEnabled = loginState.isDataValid
+
+                if (loginState.emailError != null) {
+                    emailEditText.error = getString(loginState.emailError)
+                }
+                if (loginState.passwordError != null) {
+                    passwordEditText.error = getString(loginState.passwordError)
+                }
+            })
+
+            loginViewModel.loginResult.observe(this@LoginActivity, Observer {
+                val loginResult = it ?: return@Observer
+
+                loadingProgressBar.visibility = View.GONE
+                if (loginResult.error != null) {
+                    showLoginFailed(loginResult.error)
+                }
+                if (loginResult.success != null) {
+                    updateUiWithUser(loginResult.success)
+                    setResult(RESULT_OK)
+                    finish()
+                }
+            })
+
+            emailEditText.afterTextChanged {
+                loginViewModel.loginDataChanged(
+                    emailEditText.text.toString(),
+                    passwordEditText.text.toString()
+                )
+            }
+
+            passwordEditText.apply {
+                afterTextChanged {
+                    loginViewModel.loginDataChanged(
+                        emailEditText.text.toString(),
+                        passwordEditText.text.toString()
+                    )
+                }
+
+                setOnEditorActionListener { _, actionId, _ ->
+                    when (actionId) {
+                        EditorInfo.IME_ACTION_DONE ->
+                            loginViewModel.login(
+                                emailEditText.text.toString(),
+                                passwordEditText.text.toString()
+                            )
+                    }
+                    false
+                }
+
+                loginButton.setOnClickListener {
+                    loadingProgressBar.visibility = View.VISIBLE
+                    loginViewModel.login(
+                        emailEditText.text.toString(),
+                        passwordEditText.text.toString()
+                    )
+                }
+            }
+        }
+    }
+
+    private fun updateUiWithUser(model: LoggedInUserView) {
+        val welcome = getString(R.string.welcome) + "\n" + model.email
+        // TODO : initiate successful logged in experience
+        Toast.makeText(
+            applicationContext,
+            welcome,
+            Toast.LENGTH_SHORT
+        ).show()
+        navigateToMainActivity()
+    }
+
+    private fun showLoginFailed(@StringRes errorString: Int) {
+        Toast.makeText(applicationContext, errorString, Toast.LENGTH_SHORT).show()
+    }
+
+    private fun navigateToMainActivity() {
+        val intent = Intent(this, MainActivity::class.java)
+        startActivity(intent)
+        finish()
+    }
+
+    private fun isLoggedIn(): Boolean {
+        return sharedPreferences.getBoolean("isLoggedIn", false)
+    }
+}
+
+/**
+ * Extension function to simplify setting an afterTextChanged action to EditText components.
+ */
+fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
+    this.addTextChangedListener(object : TextWatcher {
+        override fun afterTextChanged(editable: Editable?) {
+            afterTextChanged.invoke(editable.toString())
+        }
+
+        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+
+        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
+    })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/LoginFragment.kt b/app/src/main/java/com/example/bondoyap/ui/login/LoginFragment.kt
deleted file mode 100644
index 2fa73d7b619d82ec9bcd99dd44731ea250e8830a..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/example/bondoyap/ui/login/LoginFragment.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-package com.example.bondoyap.ui.login
-
-import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModelProvider
-import androidx.annotation.StringRes
-import androidx.fragment.app.Fragment
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.inputmethod.EditorInfo
-import android.widget.Toast
-import androidx.navigation.fragment.findNavController
-import com.example.bondoyap.R
-import com.example.bondoyap.databinding.FragmentLoginBinding
-
-class LoginFragment : Fragment() {
-
-    private lateinit var loginViewModel: LoginViewModel
-    private var _binding: FragmentLoginBinding? = 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? {
-
-        _binding = FragmentLoginBinding.inflate(inflater, container, false)
-        return binding.root
-
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
-            .get(LoginViewModel::class.java)
-
-        val emailEditText = binding.email
-        val passwordEditText = binding.password
-        val loginButton = binding.login
-        val loadingProgressBar = binding.loading
-
-        loginViewModel.loginFormState.observe(viewLifecycleOwner,
-            Observer { loginFormState ->
-                if (loginFormState == null) {
-                    return@Observer
-                }
-                loginButton.isEnabled = loginFormState.isDataValid
-                loginFormState.emailError?.let {
-                    emailEditText.error = getString(it)
-                }
-                loginFormState.passwordError?.let {
-                    passwordEditText.error = getString(it)
-                }
-            })
-
-        loginViewModel.loginResult.observe(viewLifecycleOwner,
-            Observer { loginResult ->
-                loginResult ?: return@Observer
-                loadingProgressBar.visibility = View.GONE
-                loginResult.error?.let {
-                    showLoginFailed(it)
-                }
-                loginResult.success?.let {
-                    updateUiWithUser(it)
-                    requireActivity().actionBar?.setDisplayHomeAsUpEnabled(false)
-                    navigateToTransactionFragment()
-                }
-            })
-
-        val afterTextChangedListener = object : TextWatcher {
-            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
-                // ignore
-            }
-
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
-                // ignore
-            }
-
-            override fun afterTextChanged(s: Editable) {
-                loginViewModel.loginDataChanged(
-                    emailEditText.text.toString(),
-                    passwordEditText.text.toString()
-                )
-            }
-        }
-        emailEditText.addTextChangedListener(afterTextChangedListener)
-        passwordEditText.addTextChangedListener(afterTextChangedListener)
-        passwordEditText.setOnEditorActionListener { _, actionId, _ ->
-            if (actionId == EditorInfo.IME_ACTION_DONE) {
-                loginViewModel.login(
-                    emailEditText.text.toString(),
-                    passwordEditText.text.toString()
-                )
-            }
-            false
-        }
-
-        loginButton.setOnClickListener {
-            loadingProgressBar.visibility = View.VISIBLE
-            loginViewModel.login(
-                emailEditText.text.toString(),
-                passwordEditText.text.toString()
-            )
-        }
-    }
-
-    private fun updateUiWithUser(model: LoggedInUserView) {
-        val welcome = getString(R.string.welcome) + model.displayName
-        // TODO : initiate successful logged in experience
-        val appContext = context?.applicationContext ?: return
-        Toast.makeText(appContext, welcome, Toast.LENGTH_LONG).show()
-    }
-
-    private fun showLoginFailed(@StringRes errorString: Int) {
-        val appContext = context?.applicationContext ?: return
-        Toast.makeText(appContext, errorString, Toast.LENGTH_LONG).show()
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        _binding = null
-    }
-
-    private fun navigateToTransactionFragment() {
-        findNavController().navigate(R.id.navigation_transactions)
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModel.kt b/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModel.kt
index 11feb87bc1487a0f9624658311d6fe0bfc11b1c6..fedbf7d9cd2c6686bf8dc47cdf25d6cf84422a40 100644
--- a/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModel.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModel.kt
@@ -4,9 +4,11 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import android.util.Patterns
+import androidx.lifecycle.viewModelScope
 import com.example.bondoyap.R
 import com.example.bondoyap.ui.login.data.LoginRepository
 import com.example.bondoyap.ui.login.data.Result
+import kotlinx.coroutines.launch
 
 class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
 
@@ -17,13 +19,14 @@ class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel()
     val loginResult: LiveData<LoginResult> = _loginResult
 
     fun login(email: String, password: String) {
-        val result = loginRepository.login(email, password)
-
-        if (result is Result.Success) {
-            _loginResult.value =
-                LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
-        } else {
-            _loginResult.value = LoginResult(error = R.string.login_failed)
+        viewModelScope.launch {
+            val result = loginRepository.login(email, password)
+            if (result is Result.Success) {
+                _loginResult.value =
+                    LoginResult(success = LoggedInUserView(email = result.data.email))
+            } else {
+                _loginResult.value = LoginResult(error = R.string.login_failed)
+            }
         }
     }
 
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModelFactory.kt b/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModelFactory.kt
index de741ade55806d4f3612cf2ef705e3d13874dc9d..0b7abfa109b689c808959179ec0729362d3c2a8d 100644
--- a/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModelFactory.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/login/LoginViewModelFactory.kt
@@ -1,22 +1,21 @@
 package com.example.bondoyap.ui.login
 
+import android.content.Context
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import com.example.bondoyap.ui.login.data.LoginDataSource
 import com.example.bondoyap.ui.login.data.LoginRepository
 
-/**
- * ViewModel provider factory to instantiate LoginViewModel.
- * Required given LoginViewModel has a non-empty constructor
- */
-class LoginViewModelFactory : ViewModelProvider.Factory {
+class LoginViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
 
     @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
             return LoginViewModel(
                 loginRepository = LoginRepository(
-                    dataSource = LoginDataSource()
+                    dataSource = LoginDataSource(context),
+                    context = context
+
                 )
             ) as T
         }
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/data/LoginDataSource.kt b/app/src/main/java/com/example/bondoyap/ui/login/data/LoginDataSource.kt
index 3e221c0a19f9dc26f316fee64c9cfae5734e84f1..00f4b562726b80158e1de36da69e93f895b347ed 100644
--- a/app/src/main/java/com/example/bondoyap/ui/login/data/LoginDataSource.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/login/data/LoginDataSource.kt
@@ -1,24 +1,58 @@
 package com.example.bondoyap.ui.login.data
 
+import android.content.Context
+import android.util.Log
+import com.example.bondoyap.service.api.ApiClient
+import com.example.bondoyap.service.api.data.LoginRequest
 import com.example.bondoyap.ui.login.data.model.LoggedInUser
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
 import java.io.IOException
 
 /**
  * Class that handles authentication w/ login credentials and retrieves user information.
  */
-class LoginDataSource {
+class LoginDataSource(private val context: Context) {
 
-    fun login(email: String, password: String): Result<LoggedInUser> {
+    private lateinit var apiClient: ApiClient
+
+    suspend fun login(email: String, password: String): Result<LoggedInUser> {
         return try {
-            // TODO: handle loggedInUser authentication
-            val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
-            Result.Success(fakeUser)
+
+            val token = postLogin(email, password)
+
+            val parts = email.split("@")
+            val nim = parts[0]
+
+            val user = LoggedInUser(nim, email, token)
+
+            Result.Success(user)
         } catch (e: Throwable) {
             Result.Error(IOException("Error logging in", e))
         }
     }
 
-    fun logout() {
-        // TODO: revoke authentication
+    private suspend fun postLogin(email: String, password: String):String {
+
+        apiClient = ApiClient()
+        val service = apiClient.getApiService(context)
+
+        return withContext(Dispatchers.IO) {
+            try {
+                val loginRequest = LoginRequest(email, password)
+                val response = service.login(loginRequest).execute()
+
+                if (response.isSuccessful) {
+                    val loginResponse = response.body()
+                    loginResponse?.token ?: throw IOException("Token not received")
+                } else {
+                    Log.d("LoginDataSource", "${response.code()} ${response.message()}")
+                    throw IOException("${response.code()} ${response.message()}")
+                }
+            } catch (e: Exception) {
+                Log.d("LoginDataSource", "Login failed: ${e.message}")
+                throw IOException("Login failed: ${e.message}")
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/data/LoginRepository.kt b/app/src/main/java/com/example/bondoyap/ui/login/data/LoginRepository.kt
index 840ffe51ce4b5e3a307ad9c7c0442c11d14642ee..1d7293786899ffa28dc67352cbcd15d0149e34ab 100644
--- a/app/src/main/java/com/example/bondoyap/ui/login/data/LoginRepository.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/login/data/LoginRepository.kt
@@ -1,34 +1,30 @@
 package com.example.bondoyap.ui.login.data
 
 import com.example.bondoyap.ui.login.data.model.LoggedInUser
+import android.content.Context
+import android.content.SharedPreferences
+import com.example.bondoyap.service.api.Constants.SHARED_PREFS_NAME
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
 
 /**
  * Class that requests authentication and user information from the remote data source and
  * maintains an in-memory cache of login status and user credentials information.
  */
 
-class LoginRepository(val dataSource: LoginDataSource) {
+class LoginRepository(val dataSource: LoginDataSource, context: Context) {
 
-    // in-memory cache of the loggedInUser object
-    var user: LoggedInUser? = null
-        private set
+    private val sharedPreferences: SharedPreferences = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
+    private val editor: SharedPreferences.Editor = sharedPreferences.edit()
 
-    val isLoggedIn: Boolean
-        get() = user != null
-
-    init {
-        // If user credentials will be cached in local storage, it is recommended it be encrypted
-        // @see https://developer.android.com/training/articles/keystore
-        user = null
-    }
+    private val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
+    private val userAdapter: JsonAdapter<LoggedInUser> = moshi.adapter(LoggedInUser::class.java)
 
-    fun logout() {
-        user = null
-        dataSource.logout()
-    }
+    val isLoggedIn: Boolean
+        get() = sharedPreferences.getBoolean("isLoggedIn", false)
 
-    fun login(email: String, password: String): Result<LoggedInUser> {
-        // handle login
+    suspend fun login(email: String, password: String): Result<LoggedInUser> {
         val result = dataSource.login(email, password)
 
         if (result is Result.Success) {
@@ -38,8 +34,22 @@ class LoginRepository(val dataSource: LoginDataSource) {
         return result
     }
 
+    fun logout(){
+        editor.clear()
+        editor.apply()
+    }
+
+    fun getUser(): LoggedInUser? {
+        val userJson = sharedPreferences.getString("loggedInUser", null)
+        return userJson?.let {
+            userAdapter.fromJson(it)
+        }
+    }
+
     private fun setLoggedInUser(loggedInUser: LoggedInUser) {
-        this.user = loggedInUser
+        val userJson = userAdapter.toJson(loggedInUser)
+        editor.putString("loggedInUser", userJson).apply()
+        editor.putBoolean("isLoggedIn", true).apply()
         // If user credentials will be cached in local storage, it is recommended it be encrypted
         // @see https://developer.android.com/training/articles/keystore
     }
diff --git a/app/src/main/java/com/example/bondoyap/ui/login/data/model/LoggedInUser.kt b/app/src/main/java/com/example/bondoyap/ui/login/data/model/LoggedInUser.kt
index 10a9aa93f64d280b3861bd282307ab0213e011df..78d7e033176d0673dfb6016ebacf1d2c54c256e0 100644
--- a/app/src/main/java/com/example/bondoyap/ui/login/data/model/LoggedInUser.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/login/data/model/LoggedInUser.kt
@@ -5,5 +5,6 @@ package com.example.bondoyap.ui.login.data.model
  */
 data class LoggedInUser(
     val userId: String,
-    val displayName: String
+    val email: String,
+    val token: String
 )
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/ScanResultFragment.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/ScanResultFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0c7913390e762e7536716cb0e985dda5702e0357
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/scanner/ScanResultFragment.kt
@@ -0,0 +1,139 @@
+package com.example.bondoyap.ui.scanner
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.viewModels
+import androidx.navigation.NavController
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.bondoyap.databinding.FragmentScanResultBinding
+import com.example.bondoyap.service.LocationManager
+import com.example.bondoyap.service.api.data.Item
+import com.example.bondoyap.ui.scanner.listAdapter.ScanResultListAdapter
+import com.example.bondoyap.ui.transactions.TransactionsApplication
+import com.example.bondoyap.ui.transactions.TransactionsViewModel
+import com.example.bondoyap.ui.transactions.TransactionsViewModelFactory
+import com.example.bondoyap.ui.transactions.data.Transactions
+import com.google.android.gms.location.LocationServices
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+
+class ScanResultFragment : Fragment() {
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private var _binding: FragmentScanResultBinding? = null
+    private val binding get() = _binding!!
+    private lateinit var navController: NavController
+
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        _binding = FragmentScanResultBinding.inflate(inflater, container, false)
+        val root: View = binding.root
+
+        navController = findNavController()
+        val scannerViewModel: ScannerViewModel by activityViewModels()
+        val items = scannerViewModel.items ?: throw Exception("Scan Result is null")
+        Log.d("ScanResult", "Result Received: $items")
+
+        val recyclerView = binding.recyclerView
+        val adapter = ScanResultListAdapter()
+        recyclerView.adapter = adapter
+        recyclerView.layoutManager = LinearLayoutManager(requireContext())
+        adapter.submitList(items)
+
+        binding.cancelButton.setOnClickListener {
+            navController.popBackStack()
+        }
+
+        binding.saveButton.setOnClickListener {
+            saveNota(items)
+        }
+
+        return root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+
+    private fun saveNota(items: List<Item>) {
+        try {
+            Log.d("ScanResult", "Saving note: $items")
+            saveNotaRepository(items)
+            Toast.makeText(requireContext(), "Nota berhasil disimpan", Toast.LENGTH_SHORT).show()
+            navController.popBackStack()
+        } catch (e: Exception) {
+            Toast.makeText(requireContext(), "Nota gagal disimpan", Toast.LENGTH_SHORT).show()
+            Log.e("ScanResult", "Saving note failed:", e)
+        }
+    }
+
+    private fun saveNotaRepository(items: List<Item>) {
+        val dateDateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+        val titleDateFormat = SimpleDateFormat("dd_MM_yyyy_HH_mm_ss", Locale.getDefault())
+        val currentTime = Date()
+        val currentDate = dateDateFormat.format(currentTime)
+        val title = titleDateFormat.format(currentTime)
+
+        val value = items.sumOf { it.quantity * it.price }
+
+        val transactionsViewModel: TransactionsViewModel by viewModels {
+            TransactionsViewModelFactory((requireContext().applicationContext as TransactionsApplication).repository)
+        }
+
+        LocationManager.askLocationPermission(requireContext(), requireActivity())
+        Log.d("ScanResult", "Getting location")
+
+        if (LocationManager.haveLocationPermission(requireContext())) {
+            Log.d("ScanResult", "Saving note on database")
+            Log.d("LocationManager", "Getting last location")
+            val fusedLocationProviderClient =
+                LocationServices.getFusedLocationProviderClient(requireContext())
+
+            @SuppressLint("MissingPermission")
+            val locationProvider = fusedLocationProviderClient.lastLocation
+            locationProvider.addOnSuccessListener {
+                if (it != null) {
+                    Log.d(
+                        "LocationManager",
+                        "Updating location to latitude: ${it.latitude} and longitude: ${it.longitude}"
+                    )
+                    val transaction = Transactions(
+                        judul = "Scanner_${title}",
+                        nominal = value,
+                        isPemasukan = false,
+                        tanggal = currentDate,
+                        longitude = it.longitude.toString(),
+                        latitude = it.latitude.toString()
+                    )
+                    transactionsViewModel.upsert(transaction)
+                }
+            }
+        } else {
+            Log.d("ScanResult", "Saving note on database")
+            val transaction = Transactions(
+                judul = "Scanner_${title}",
+                nominal = value,
+                isPemasukan = false,
+                tanggal = currentDate,
+                longitude = "",
+                latitude = ""
+            )
+            transactionsViewModel.upsert(transaction)
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt
index 0996dc927745fd359a7ac320a90b845d8ffb9083..94353b0bae865f894553b87498625e90e009a073 100644
--- a/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerFragment.kt
@@ -1,37 +1,144 @@
 package com.example.bondoyap.ui.scanner
 
+import android.Manifest
+import android.app.Activity
+import android.content.ContentResolver
+import android.content.Intent
+import android.net.Uri
 import android.os.Bundle
+import android.provider.MediaStore
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.widget.TextView
+import androidx.activity.result.ActivityResultLauncher
+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.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.NavController
+import androidx.navigation.fragment.findNavController
+import com.example.bondoyap.R
 import com.example.bondoyap.databinding.FragmentScannerBinding
+import com.example.bondoyap.service.api.ApiClient
+import com.example.bondoyap.service.api.data.BillResponse
+import com.example.bondoyap.service.api.data.Items
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.io.File
+import java.io.FileOutputStream
 
-class ScannerFragment : Fragment() {
-
-    private var _binding: FragmentScannerBinding? = null
 
+class ScannerFragment : Fragment() {
     // This property is only valid between onCreateView and
     // onDestroyView.
+    private var _binding: FragmentScannerBinding? = null
     private val binding get() = _binding!!
+    private lateinit var navController: NavController
+
+    // Camera
+    private var isBackCamera = true
+    private var frozenPreview: Boolean = false
+    private var cameraImageFile: File? = null
+    private lateinit var imageCapture: ImageCapture
+    private lateinit var cameraLauncher: ActivityResultLauncher<String>
+
+    // Gallery
+    private lateinit var changeImage: ActivityResultLauncher<Intent>
+    private lateinit var pickImageLauncher: ActivityResultLauncher<String>
+
+    // Upload
+    private lateinit var cacheDir: File
+    private lateinit var contentResolver: ContentResolver
+    private lateinit var apiClient: ApiClient
+
 
     override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
     ): View {
-        val scannerViewModel =
-                ViewModelProvider(this).get(ScannerViewModel::class.java)
-
         _binding = FragmentScannerBinding.inflate(inflater, container, false)
         val root: View = binding.root
 
-        val textView: TextView = binding.textScanner
-        scannerViewModel.text.observe(viewLifecycleOwner) {
-            textView.text = it
+        navController = findNavController()
+        cacheDir = requireContext().cacheDir
+        contentResolver = requireContext().contentResolver
+        apiClient = ApiClient()
+
+        cameraLauncher = registerForActivityResult(
+            ActivityResultContracts.RequestPermission()
+        ) { isGranted ->
+            if (isGranted) {
+                startCamera()
+            }
+        }
+
+        changeImage = registerForActivityResult(
+            ActivityResultContracts.StartActivityForResult()
+        ) {
+            if (it.resultCode == Activity.RESULT_OK) {
+                try {
+                    val data = it.data
+                    val imgUri = data?.data ?: throw Exception("Image Uri is null")
+                    Log.d("ImageInput", "Image Selected with URI: $imgUri")
+
+                    // Copying image from external directory to cache
+                    val tempFile = File.createTempFile("Gallery_Image", ".jpg", cacheDir)
+                    val inputStream = contentResolver.openInputStream(imgUri)
+                    val outputStream = FileOutputStream(tempFile)
+                    inputStream?.use { input ->
+                        outputStream.use { output ->
+                            input.copyTo(output)
+                        }
+                    }
+
+                    uploadPhoto(tempFile)
+                } catch (e: Exception) {
+                    Log.e("ImageInput", "Image Input Failed:", e)
+                }
+            }
+        }
+
+        val pickImageIntent =
+            Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI)
+        pickImageLauncher = registerForActivityResult(
+            ActivityResultContracts.RequestPermission()
+        ) { isGranted ->
+            if (isGranted) {
+                changeImage.launch(pickImageIntent)
+            }
+        }
+
+        imageCapture = ImageCapture.Builder().build()
+        cameraLauncher.launch(Manifest.permission.CAMERA)
+
+        binding.switchCameraButton.setOnClickListener {
+            toggleCamera()
+        }
+
+        binding.captureButton.setOnClickListener {
+            freezePreview()
+        }
+
+        binding.galleryButton.setOnClickListener {
+            pickImageLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
+        }
+
+        binding.uploadButton.isClickable = false
+        binding.uploadButton.setOnClickListener {
+            cameraImageFile?.let { it1 -> uploadPhoto(it1) }
         }
+
         return root
     }
 
@@ -39,4 +146,108 @@ class ScannerFragment : Fragment() {
         super.onDestroyView()
         _binding = null
     }
+
+    private fun startCamera() {
+        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
+        cameraProviderFuture.addListener({
+            val cameraProvider = cameraProviderFuture.get()
+
+            val preview = Preview.Builder().build().also { mPreview ->
+                if (!frozenPreview) {
+                    mPreview.setSurfaceProvider(binding.previewView.surfaceProvider)
+                } else {
+                    mPreview.setSurfaceProvider(null)
+                }
+            }
+
+            val imageCapture = ImageCapture.Builder()
+                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
+                .build()
+
+            val cameraSelector = if (isBackCamera) {
+                CameraSelector.DEFAULT_BACK_CAMERA
+            } else {
+                CameraSelector.DEFAULT_FRONT_CAMERA
+            }
+
+            try {
+                cameraProvider?.unbindAll()
+                cameraProvider?.bindToLifecycle(this, cameraSelector, preview, imageCapture)
+
+                val tempFile = File.createTempFile("Camera_Image", ".jpg", cacheDir)
+                val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build()
+                imageCapture.takePicture(outputOptions,
+                    ContextCompat.getMainExecutor(requireContext()),
+                    object : ImageCapture.OnImageSavedCallback {
+                        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+                            val uriImg = Uri.fromFile(tempFile)
+                            cameraImageFile = tempFile
+                            Log.d("CameraX", "Image captured: $uriImg")
+                        }
+
+                        override fun onError(exception: ImageCaptureException) {
+                            Log.e(
+                                "CameraX",
+                                "Error capturing image: ${exception.message}",
+                                exception
+                            )
+                        }
+                    }
+                )
+            } catch (e: Exception) {
+                Log.e("CameraX", "Starting Camera Failed:", e)
+            }
+        }, ContextCompat.getMainExecutor(requireContext()))
+    }
+
+    private fun toggleCamera() {
+        isBackCamera = !isBackCamera
+        cameraLauncher.launch(Manifest.permission.CAMERA)
+    }
+
+    private fun freezePreview() {
+        frozenPreview = !frozenPreview
+        if (!frozenPreview) {
+            binding.captureButton.text = "Capture"
+            binding.uploadButton.isClickable = false
+        } else {
+            binding.captureButton.text = "Retake"
+            binding.uploadButton.isClickable = true
+        }
+        cameraLauncher.launch(Manifest.permission.CAMERA)
+    }
+
+    private fun uploadPhoto(photo: File) {
+        try {
+            val requestFile = photo.asRequestBody("image/*".toMediaTypeOrNull())
+            val requestBody = MultipartBody.Part.createFormData("file", photo.name, requestFile)
+            val apiCall = apiClient.getApiService(requireContext()).getBill(requestBody)
+            apiCall.enqueue(object : Callback<BillResponse> {
+                override fun onResponse(
+                    call: Call<BillResponse>, response: Response<BillResponse>
+                ) {
+                    if (response.isSuccessful) {
+                        val billResponse =
+                            response.body() ?: throw Exception("Bill Response is Empty")
+                        Log.d("BillUpload", "Server Response: $billResponse")
+                        navigateScanResult(billResponse.items)
+                    } else {
+                        Log.e("BillUpload", "Error: ${response.code()}")
+                    }
+                }
+
+                override fun onFailure(call: Call<BillResponse>, t: Throwable) {
+                    Log.e("BillUpload", "Failed to upload bill", t)
+                }
+            })
+        } catch (e: Exception) {
+            Log.e("BillUpload", "Bill Upload Failed:", e)
+        }
+    }
+
+    private fun navigateScanResult(items: Items) {
+        val scannerViewModel: ScannerViewModel by activityViewModels()
+        scannerViewModel.items = items.items
+        navController.navigate(R.id.action_to_scanResultFragment)
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerViewModel.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerViewModel.kt
index ba02a0bc2a2ac16f37abfefc81ad9afdc7cd6a0f..3e659568fd7e9ca0d091f8b24e1c7703777b3dc6 100644
--- a/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerViewModel.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/scanner/ScannerViewModel.kt
@@ -3,11 +3,14 @@ package com.example.bondoyap.ui.scanner
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import com.example.bondoyap.service.api.data.Item
 
 class ScannerViewModel : ViewModel() {
+    var items: List<Item>? = null
 
     private val _text = MutableLiveData<String>().apply {
         value = "This is Scanner Fragment"
     }
     val text: LiveData<String> = _text
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ItemDiffCallback.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ItemDiffCallback.kt
new file mode 100644
index 0000000000000000000000000000000000000000..15e214aa61d0894a9bc13b086ea6c9845571a7de
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ItemDiffCallback.kt
@@ -0,0 +1,17 @@
+package com.example.bondoyap.ui.scanner.listAdapter
+
+import androidx.recyclerview.widget.DiffUtil
+import com.example.bondoyap.service.api.data.Item
+
+class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {
+    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
+        val isNameSame = oldItem.name == newItem.name
+        val isQuantitySame = oldItem.quantity == newItem.quantity
+        val isPriceSame = oldItem.price == newItem.price
+        return isNameSame && isQuantitySame && isPriceSame
+    }
+
+    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
+        return oldItem == newItem
+    }
+}
diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ItemViewHolder.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ItemViewHolder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3c751de3299b6cb36e794fe7c31cf07999f5943c
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ItemViewHolder.kt
@@ -0,0 +1,29 @@
+package com.example.bondoyap.ui.scanner.listAdapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.example.bondoyap.R
+import com.example.bondoyap.service.api.data.Item
+
+class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+    fun bind(item: Item) {
+        val itemName: TextView = itemView.findViewById(R.id.item_name)
+        val itemQuantity: TextView = itemView.findViewById(R.id.item_quantity)
+        val itemPrice: TextView = itemView.findViewById(R.id.item_price)
+
+        itemName.text = "Nama: ${item.name}"
+        itemQuantity.text = "Jumlah: ${item.quantity}"
+        itemPrice.text = "Harga: ${item.price}"
+    }
+
+    companion object {
+        fun create(parent: ViewGroup): ItemViewHolder {
+            val view: View = LayoutInflater.from(parent.context)
+                .inflate(R.layout.recyclerview_scan_result, parent, false)
+            return ItemViewHolder(view)
+        }
+    }
+}
diff --git a/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ScanResultListAdapter.kt b/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ScanResultListAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d60897087cbdcf1d42dc7263bc9495e249317a7e
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/scanner/listAdapter/ScanResultListAdapter.kt
@@ -0,0 +1,17 @@
+package com.example.bondoyap.ui.scanner.listAdapter
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.ListAdapter
+import com.example.bondoyap.service.api.data.Item
+
+class ScanResultListAdapter :
+    ListAdapter<Item, ItemViewHolder>(ItemDiffCallback()) {
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
+        return ItemViewHolder.create(parent)
+    }
+
+    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
+        val currentItem = getItem(position)
+        holder.bind(currentItem)
+    }
+}
diff --git a/app/src/main/java/com/example/bondoyap/ui/settings/SettingsFragment.kt b/app/src/main/java/com/example/bondoyap/ui/settings/SettingsFragment.kt
index 9433c1071c0e9bc4cd8fcac9326e5c7b3acc43cc..4f415981d062e9ffd6f5e1af86636b4a812c704d 100644
--- a/app/src/main/java/com/example/bondoyap/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/settings/SettingsFragment.kt
@@ -1,38 +1,133 @@
 package com.example.bondoyap.ui.settings
 
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.Bundle
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.widget.TextView
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
 import androidx.lifecycle.ViewModelProvider
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
 import com.example.bondoyap.databinding.FragmentSettingsBinding
+import com.example.bondoyap.service.api.Constants.ACTION_RANDOMIZE_TRANSACTIONS
+import com.example.bondoyap.ui.login.LoginActivity
+import com.example.bondoyap.ui.transactions.TransactionsApplication
+import com.example.bondoyap.ui.transactions.TransactionsViewModel
+import com.example.bondoyap.ui.transactions.TransactionsViewModelFactory
+import android.Manifest
 
 class SettingsFragment : Fragment() {
 
     private var _binding: FragmentSettingsBinding? = null
-
-    // This property is only valid between onCreateView and
-    // onDestroyView.
     private val binding get() = _binding!!
+    private lateinit var settingsViewModel: SettingsViewModel
+
+    private val transactionsViewModel: TransactionsViewModel by viewModels {
+        TransactionsViewModelFactory((requireContext().applicationContext as TransactionsApplication).repository)
+    }
 
     override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
     ): View {
-        val settingsViewModel =
-                ViewModelProvider(this).get(SettingsViewModel::class.java)
-
         _binding = FragmentSettingsBinding.inflate(inflater, container, false)
-        val root: View = binding.root
 
-        val textView: TextView = binding.textSettings
-        settingsViewModel.text.observe(viewLifecycleOwner) {
-            textView.text = it
+        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE)
+        }
+
+        return binding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        val factory = context?.let { SettingsViewModelFactory(it) }
+        if (factory != null) {
+            settingsViewModel = ViewModelProvider(this, factory)[SettingsViewModel::class.java]
+        } else {
+            throw IllegalStateException("Context is null. Cannot create SettingsViewModelFactory.")
+        }
+        val textView = binding.textSettings
+        val logoutButton = binding.logoutButton
+        val randomButton = binding.randomTransactions
+        val saveButton = binding.saveTransactions
+        val sendButton = binding.sendTransactions
+
+        settingsViewModel.getUser()?.let { user ->
+            val loggedInUserText = "Masuk dengan akun:\n ${user.email}"
+            textView.text = loggedInUserText
+        }
+        val appContext = context?.applicationContext
+
+        logoutButton.setOnClickListener {
+            settingsViewModel.getUser()
+            settingsViewModel.logout()
+//            findNavController().navigate(R.id.navigation_login)
+
+            val activity = requireActivity()
+            val intent = Intent(activity, LoginActivity::class.java)
+            Toast.makeText(appContext, "Logout Sukses!", Toast.LENGTH_SHORT).show()
+            activity.startActivity(intent)
+            activity.finish()
         }
-        return root
+
+        randomButton.setOnClickListener {
+            Toast.makeText(appContext, "Membuat transaksi random ...", Toast.LENGTH_SHORT).show()
+
+            val intent = Intent(ACTION_RANDOMIZE_TRANSACTIONS)
+            intent.putExtra("message", "Randomize from setting!")
+            LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent)
+
+            Log.d("BroadcastDebug", "Sending broadcast from SettingsFragment")
+        }
+
+        val exporter = TransactionsExporter(transactionsViewModel, requireContext())
+
+        saveButton.setOnClickListener {
+            if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                Toast.makeText(appContext, "Allow storage permission untuk menyimpan transaksi ke file xls/xlsx", Toast.LENGTH_SHORT).show()
+            } else {
+                val formats = arrayOf("XLS", "XLSX")
+                val builder = AlertDialog.Builder(requireContext())
+                builder.setTitle("Pilih Format File")
+                builder.setItems(formats) { dialog: DialogInterface, which: Int ->
+                    when (which) {
+                        0 -> {
+                            Toast.makeText(appContext, "Menyimpan transaksi ke xls...", Toast.LENGTH_SHORT).show()
+                            exporter.exportToXLS()
+                            Toast.makeText(appContext, "Penyimpanan xls pada folder Documents berhasil...", Toast.LENGTH_SHORT).show()
+                        }
+                        1 -> {
+                            Toast.makeText(appContext, "Menyimpan transaksi ke xlsx...", Toast.LENGTH_SHORT).show()
+                            exporter.exportToXLSX()
+                            Toast.makeText(appContext, "Penyimpanan xlsx pada folder Documents berhasil...", Toast.LENGTH_SHORT).show()
+                        }
+                    }
+                    dialog.dismiss()
+                }
+                builder.create().show()
+            }
+        }
+
+        sendButton.setOnClickListener {
+            Toast.makeText(appContext, "Mengirimkan transaksi ...", Toast.LENGTH_SHORT).show()
+            //todo
+        }
+
+    }
+
+    companion object {
+        private const val PERMISSION_REQUEST_CODE = 1001
     }
 
     override fun onDestroyView() {
diff --git a/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModel.kt
index 02f1e625446ea7b7ced397b4d01dea34bc08185c..126d2f8867aa68b32386f7eb25275f64d1044596 100644
--- a/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModel.kt
@@ -3,11 +3,20 @@ package com.example.bondoyap.ui.settings
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import com.example.bondoyap.ui.login.data.LoginRepository
+import com.example.bondoyap.ui.login.data.model.LoggedInUser
 
-class SettingsViewModel : ViewModel() {
+class SettingsViewModel(private val loginRepository: LoginRepository) : ViewModel() {
 
     private val _text = MutableLiveData<String>().apply {
         value = "This is Settings Fragment"
     }
     val text: LiveData<String> = _text
+
+    fun logout(){
+        loginRepository.logout()
+    }
+    fun getUser(): LoggedInUser? {
+        return loginRepository.getUser()
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModelFactory.kt b/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModelFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ae367283563ca18363bc458743d2df24b2c51472
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/settings/SettingsViewModelFactory.kt
@@ -0,0 +1,23 @@
+package com.example.bondoyap.ui.settings
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.example.bondoyap.ui.login.data.LoginDataSource
+import com.example.bondoyap.ui.login.data.LoginRepository
+
+class SettingsViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        if (modelClass.isAssignableFrom(SettingsViewModel::class.java)) {
+            return SettingsViewModel(
+                loginRepository = LoginRepository(
+                    dataSource = LoginDataSource(context),
+                    context = context
+                )
+            ) as T
+        }
+        throw IllegalArgumentException("Unknown ViewModel class")
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/settings/TransactionsExporter.kt b/app/src/main/java/com/example/bondoyap/ui/settings/TransactionsExporter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..05c76159446de6ac39e1ac0589deff017b8bfe8c
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/settings/TransactionsExporter.kt
@@ -0,0 +1,98 @@
+package com.example.bondoyap.ui.settings
+
+import android.content.Context
+import android.location.Address
+import android.location.Geocoder
+import android.os.Environment
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.lifecycle.viewModelScope
+import com.example.bondoyap.ui.transactions.TransactionsViewModel
+import com.example.bondoyap.ui.transactions.data.Transactions
+import io.github.evanrupert.excelkt.Sheet
+import io.github.evanrupert.excelkt.workbook
+import kotlinx.coroutines.launch
+import org.apache.poi.ss.usermodel.FillPatternType
+import org.apache.poi.ss.usermodel.IndexedColors
+import java.io.File
+import java.util.Locale
+
+class TransactionsExporter(private val transactionsViewModel: TransactionsViewModel, val context: Context) {
+    fun exportToXLS() {
+        transactionsViewModel.viewModelScope.launch {
+            val transactions = transactionsViewModel.getAllTransactionsList()
+            Log.d("SaveDebug", "xls function")
+            writeToExcel("transactions.xls", transactions)
+        }
+    }
+
+    fun exportToXLSX() {
+        transactionsViewModel.viewModelScope.launch {
+            val transactions = transactionsViewModel.getAllTransactionsList()
+            Log.d("SaveDebug", "xlsx function")
+            writeToExcel("transactions.xlsx", transactions)
+        }
+    }
+
+    private fun writeToExcel(fileName: String, transactions: List<Transactions>) {
+        val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
+        val file = File(downloadsDir, fileName)
+
+        workbook {
+            sheet("Transactions") {
+                transactionsHeader()
+
+                for (transaction in transactions) {
+                    row {
+                        cell(transaction.tanggal)
+                        cell(
+                            if (transaction.isPemasukan) {
+                                "Pemasukan"
+                            } else {
+                                "Pengeluaran"
+                            }
+                        )
+                        cell(transaction.nominal)
+                        cell(transaction.judul)
+                        cell(if (transaction.longitude.isEmpty() || transaction.latitude.isEmpty()) {
+                            "Unavailable"
+                        } else {
+                            val addresses: List<Address> =
+                                Geocoder(context, Locale.getDefault()).getFromLocation(
+                                    transaction.latitude.toDouble(),
+                                    transaction.longitude.toDouble(),
+                                    1
+                                ) ?: emptyList()
+                            if (addresses.isNotEmpty()) {
+                                val locationName = addresses[0].getAddressLine(0)
+                                locationName
+                            } else {
+                                "Unavailable"
+                            }
+                        })
+                    }
+                }
+            }
+        }.write(file.absolutePath)
+    }
+
+
+    private fun Sheet.transactionsHeader() {
+        val headings = listOf("Tanggal", "Kategori Transaksi", "Nominal Transaksi", "Nama Transaksi", "Lokasi")
+
+        val headingStyle = createCellStyle {
+            setFont(createFont {
+                fontName = "IMPACT"
+                color = IndexedColors.BLACK.index
+            })
+
+            fillPattern = FillPatternType.SOLID_FOREGROUND
+            fillForegroundColor = IndexedColors.YELLOW.index
+        }
+
+        row(headingStyle) {
+            headings.forEach { cell(it) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/AddTransactionsFragment.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/AddTransactionsFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3179e81a058d04067bf17a8fb0807344e3d502bc
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/AddTransactionsFragment.kt
@@ -0,0 +1,128 @@
+package com.example.bondoyap.ui.transactions
+
+import android.R
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.example.bondoyap.databinding.FragmentAddTransactionsBinding
+import com.example.bondoyap.service.LocationManager
+import com.example.bondoyap.ui.transactions.data.Transactions
+import com.google.android.gms.location.LocationServices
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class AddTransactionsFragment : Fragment() {
+
+    private var _binding: FragmentAddTransactionsBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
+    private val transactionsViewModel: TransactionsViewModel by viewModels {
+        TransactionsViewModelFactory((requireContext().applicationContext as TransactionsApplication).repository)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        _binding = FragmentAddTransactionsBinding.inflate(inflater, container, false)
+
+        val pemasukan = "Pemasukan"
+        val pengeluaran = "Pengeluaran"
+
+        val categories = arrayOf(pemasukan, pengeluaran)
+        val adapter = ArrayAdapter(requireContext(), R.layout.simple_spinner_item, categories)
+
+        adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
+        binding.spinnerKategori.adapter = adapter
+
+        LocationManager.askLocationPermission(requireContext(), requireActivity())
+
+        binding.buttonSimpan.setOnClickListener {
+            val isPemasukan: Boolean = when (binding.spinnerKategori.selectedItem.toString()) {
+                pemasukan -> true
+                else -> false
+            }
+
+            val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+            val currentDate = dateFormat.format(Date())
+
+            val judul = if (binding.editTextJudul.text.toString().trim().isNotEmpty()) {
+                binding.editTextJudul.text.toString()
+            } else {
+                "Untitled"
+            }
+
+            val nominal = if (binding.editTextNominal.text.toString().trim().isNotEmpty()) {
+                binding.editTextNominal.text.toString().toDouble()
+            } else {
+                0.0
+            }
+
+            LocationManager.askLocationPermission(requireContext(), requireActivity())
+
+            if (LocationManager.haveLocationPermission(requireContext())) {
+                Log.d("ScanResult", "Saving note on database")
+                Log.d("LocationManager", "Getting last location")
+                val fusedLocationProviderClient =
+                    LocationServices.getFusedLocationProviderClient(requireContext())
+
+                @SuppressLint("MissingPermission")
+                val locationProvider = fusedLocationProviderClient.lastLocation
+                locationProvider.addOnSuccessListener {
+                    if (it != null) {
+                        Log.d(
+                            "LocationManager",
+                            "Updating location to latitude: ${it.latitude} and longitude: ${it.longitude}"
+                        )
+                        val transaction = Transactions(
+                            judul = judul,
+                            nominal = nominal,
+                            isPemasukan = isPemasukan,
+                            tanggal = currentDate,
+                            longitude = it.longitude.toString(),
+                            latitude = it.latitude.toString()
+                        )
+                        transactionsViewModel.upsert(transaction)
+                    }
+                }
+            } else {
+                Log.d("ScanResult", "Saving note on database")
+                val transaction = Transactions(
+                    judul = judul,
+                    nominal = nominal,
+                    isPemasukan = isPemasukan,
+                    tanggal = currentDate,
+                    longitude = "",
+                    latitude = ""
+                )
+                transactionsViewModel.upsert(transaction)
+            }
+
+            Toast.makeText(requireContext(), "Transaksi berhasil disimpan", Toast.LENGTH_SHORT)
+                .show()
+
+            binding.editTextJudul.text.clear()
+            binding.editTextNominal.text.clear()
+            binding.spinnerKategori.setSelection(0)
+        }
+
+        return binding.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/EditTransactionsFragment.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/EditTransactionsFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d0b9e61cd7c193ba3e5f41a0b3fb821c180a4094
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/EditTransactionsFragment.kt
@@ -0,0 +1,236 @@
+package com.example.bondoyap.ui.transactions
+
+import android.R
+import android.annotation.SuppressLint
+import android.app.AlertDialog
+import android.location.Address
+import android.location.Geocoder
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.text.SpannableStringBuilder
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.navigation.fragment.findNavController
+import com.example.bondoyap.databinding.FragmentEditTransactionsBinding
+import com.example.bondoyap.service.LocationManager
+import com.example.bondoyap.ui.transactions.data.Transactions
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationServices
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.launch
+import java.util.Locale
+
+class EditTransactionsFragment : Fragment() {
+
+    private var _binding: FragmentEditTransactionsBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
+    private val transactionsViewModel: TransactionsViewModel by viewModels {
+        TransactionsViewModelFactory((requireContext().applicationContext as TransactionsApplication).repository)
+    }
+
+    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        _binding = FragmentEditTransactionsBinding.inflate(inflater, container, false)
+
+        var tanggal = ""
+        var latitude = ""
+        var longitude = ""
+
+        val pemasukan = "Pemasukan"
+        val pengeluaran = "Pengeluaran"
+
+        val categories = arrayOf(pemasukan, pengeluaran)
+        val adapter = ArrayAdapter(requireContext(), R.layout.simple_spinner_item, categories)
+
+        adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
+        binding.spinnerKategori.adapter = adapter
+
+        binding.spinnerKategori.isEnabled = false
+        binding.editTextLokasi.isEnabled = false
+
+        val transactionId: Int = arguments?.getInt("transaction_id") ?: -1
+
+        CoroutineScope(Main).launch {
+
+            val originalTransaction: Transactions = transactionsViewModel.get(transactionId)
+
+            binding.editTextJudul.text = SpannableStringBuilder(originalTransaction.judul)
+            binding.editTextNominal.text =
+                SpannableStringBuilder(originalTransaction.nominal.toBigDecimal().toString())
+
+            if (originalTransaction.isPemasukan) {
+                binding.spinnerKategori.setSelection(categories.indexOf(pemasukan))
+            } else {
+                binding.spinnerKategori.setSelection(categories.indexOf(pengeluaran))
+            }
+
+            tanggal = originalTransaction.tanggal
+            longitude = originalTransaction.longitude
+            latitude = originalTransaction.latitude
+
+            if (originalTransaction.longitude.isEmpty() || originalTransaction.latitude.isEmpty()) {
+                binding.editTextLokasi.text = SpannableStringBuilder("Unavailable")
+            } else {
+                val addresses: List<Address> =
+                    Geocoder(requireContext(), Locale.getDefault()).getFromLocation(
+                        originalTransaction.latitude.toDouble(),
+                        originalTransaction.longitude.toDouble(),
+                        1
+                    ) ?: emptyList()
+                if (addresses.isNotEmpty()) {
+                    val locationName = addresses[0].getAddressLine(0)
+                    Handler(Looper.getMainLooper()).post {
+                        binding.editTextLokasi.text = SpannableStringBuilder(locationName)
+                    }
+                }
+            }
+        }
+
+        LocationManager.askLocationPermission(requireContext(), requireActivity())
+        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(requireContext())
+
+        binding.checkboxUpdateLokasi.setOnCheckedChangeListener { _, isChecked ->
+            if (isChecked) {
+                if (!LocationManager.haveLocationPermission(requireContext())) {
+                    binding.checkboxUpdateLokasi.isChecked = false
+                    LocationManager.askLocationPermission(requireContext(), requireActivity())
+                }
+            }
+        }
+
+        binding.buttonUpdate.setOnClickListener {
+            val isPemasukan: Boolean = when (binding.spinnerKategori.selectedItem.toString()) {
+                pemasukan -> true
+                else -> false
+            }
+
+            val judul = if (binding.editTextJudul.text.toString().trim().isNotEmpty()) {
+                binding.editTextJudul.text.toString()
+            } else {
+                "Untitled"
+            }
+
+            val nominal = if (binding.editTextNominal.text.toString().trim().isNotEmpty()) {
+                binding.editTextNominal.text.toString().toDouble()
+            } else {
+                0.0
+            }
+
+            val transaction = Transactions(
+                judul = judul,
+                nominal = nominal,
+                isPemasukan = isPemasukan,
+                tanggal = tanggal,
+                longitude = longitude,
+                latitude = latitude,
+                id = transactionId
+            )
+
+            if (binding.checkboxUpdateLokasi.isChecked) {
+                @SuppressLint("MissingPermission")
+                val location = fusedLocationProviderClient.lastLocation
+                location.addOnSuccessListener { loc ->
+                    if (loc != null) {
+                        latitude = loc.latitude.toString()
+                        longitude = loc.longitude.toString()
+
+                        transaction.latitude = latitude
+                        transaction.longitude = longitude
+                    }
+                }
+            }
+
+            showConfirmationDialog("Update", "Apakah Anda yakin ingin memperbarui transaksi ini?") {
+                transactionsViewModel.upsert(transaction)
+
+                binding.editTextJudul.text = SpannableStringBuilder(transaction.judul)
+                binding.editTextNominal.text =
+                    SpannableStringBuilder(transaction.nominal.toBigDecimal().toString())
+
+                if (transaction.longitude.isEmpty() || transaction.latitude.isEmpty()) {
+                    binding.editTextLokasi.text = SpannableStringBuilder("Unavailable")
+                } else {
+                    val addresses: List<Address> =
+                        Geocoder(requireContext(), Locale.getDefault()).getFromLocation(
+                            transaction.latitude.toDouble(),
+                            transaction.longitude.toDouble(),
+                            1
+                        ) ?: emptyList()
+                    if (addresses.isNotEmpty()) {
+                        val locationName = addresses[0].getAddressLine(0)
+                        Handler(Looper.getMainLooper()).post {
+                            binding.editTextLokasi.text = SpannableStringBuilder(locationName)
+                        }
+                    }
+                }
+
+                if (transaction.isPemasukan) {
+                    binding.spinnerKategori.setSelection(categories.indexOf(pemasukan))
+                } else {
+                    binding.spinnerKategori.setSelection(categories.indexOf(pengeluaran))
+                }
+                Toast.makeText(
+                    requireContext(),
+                    "Transaksi berhasil diperbarui",
+                    Toast.LENGTH_SHORT
+                ).show()
+            }
+
+
+        }
+
+        binding.buttonHapus.setOnClickListener {
+            val transaction = Transactions(
+                judul = "",
+                nominal = 0.0,
+                isPemasukan = false,
+                tanggal = "",
+                id = transactionId
+            )
+
+            showConfirmationDialog("Hapus", "Apakah Anda yakin ingin menghapus transaksi ini?") {
+                transactionsViewModel.delete(transaction)
+                findNavController().navigate(com.example.bondoyap.R.id.navigation_transactions)
+                Toast.makeText(requireContext(), "Transaksi berhasil dihapus", Toast.LENGTH_SHORT)
+                    .show()
+            }
+        }
+
+        return binding.root
+    }
+
+    private fun showConfirmationDialog(title: String, message: String, action: () -> Unit) {
+        val builder = AlertDialog.Builder(requireContext())
+        builder.setTitle(title)
+        builder.setMessage(message)
+        builder.setPositiveButton("Ya") { _, _ ->
+            action.invoke()
+        }
+        builder.setNegativeButton("Tidak") { dialog, _ ->
+            dialog.dismiss()
+        }
+        val dialog = builder.create()
+        dialog.show()
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsApplication.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsApplication.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0634b8129e6625beb80b829d449e8f6219f26988
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsApplication.kt
@@ -0,0 +1,10 @@
+package com.example.bondoyap.ui.transactions
+
+import android.app.Application
+import com.example.bondoyap.ui.transactions.data.TransactionsRepository
+import com.example.bondoyap.ui.transactions.data.TransactionsRoomDatabase
+
+class TransactionsApplication: Application() {
+    val database by lazy { TransactionsRoomDatabase.getDatabase(this) }
+    val repository by lazy { TransactionsRepository(database.transactionsDao()) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsBroadcastReceiver.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsBroadcastReceiver.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fa5d8fc4bf947d2ee073ebd84544518b6c806e9a
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsBroadcastReceiver.kt
@@ -0,0 +1,83 @@
+package com.example.bondoyap.ui.transactions
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.util.Log
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import com.example.bondoyap.ui.transactions.data.Transactions
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationServices
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import kotlin.random.Random
+
+class TransactionsBroadcastReceiver(private val transactionsViewModel: TransactionsViewModel) : BroadcastReceiver() {
+    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
+    private lateinit var latitude: String
+    private lateinit var longitude: String
+
+    private val listJudulTransaksi = listOf(
+        "Belanja Bulanan", "Gajian", "THR", "Tagihan Listrik", "Tagihan Air", "Tagihan Internet", "Kebutuhan Dapur", "Pakaian", "Elektronik", "Makanan",
+        "Bahan Bakar", "Cicilan", "Asuransi", "Pajak", "Angsuran Kredit", "Tiket Transportasi", "Tiket Konser", "Biaya Pendidikan", "Sewa Rumah", "Tagihan Kartu Kredit",
+        "Gaji Bonus", "Infaq", "Zakat", "Donasi", "Uang Saku", "Tabungan", "Investasi", "Liburan", "Rekreasi", "Hadiah",
+        "Hutang Lunas", "Pinjaman Lunas", "Pensiun", "Bonus Tahunan", "Uang Jajan", "Royalti", "Hadiah Ulang Tahun", "Bayar Utang", "Refund", "Uang Lebaran",
+        "Uang Jalan", "Uang Makan", "Uang Sakit", "Uang Pemberian", "Uang Saku Anak", "Pensiun Dini", "Komisi", "Tunai Back"
+    )
+
+    override fun onReceive(context: Context?, intent: Intent?) {
+        Log.d("BroadcastDebug", "Broadcast received in AddTransactionsFragment")
+
+        context ?: return
+
+        val randomJudul = listJudulTransaksi[Random.nextInt(listJudulTransaksi.size)]
+        val randomNominal = Random.nextDouble(1000000000000000.0)
+        val randomIsPemasukan = Random.nextBoolean()
+        val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+        val currentDate = dateFormat.format(Date())
+
+        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
+        val location = fusedLocationProviderClient.lastLocation
+        location.addOnSuccessListener {
+            if(it != null) {
+                latitude = it.latitude.toString()
+                longitude = it.longitude.toString()
+
+                val transaction: Transactions = Transactions(
+                    judul = randomJudul,
+                    nominal = randomNominal,
+                    isPemasukan = randomIsPemasukan,
+                    tanggal = currentDate,
+                    longitude = longitude,
+                    latitude = latitude
+                )
+                transactionsViewModel.upsert(transaction)
+            }
+        }
+
+        if(
+            ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION)
+            != PackageManager.PERMISSION_GRANTED
+            && ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION)
+            != PackageManager.PERMISSION_GRANTED
+        ) {
+            val transaction: Transactions = Transactions(
+                judul = randomJudul,
+                nominal = randomNominal,
+                isPemasukan = randomIsPemasukan,
+                tanggal = currentDate,
+                longitude = "",
+                latitude = ""
+            )
+            transactionsViewModel.upsert(transaction)
+            Toast.makeText(context.applicationContext,
+                "Izinkan location permission untuk membuat transaksi random dengan lokasi",
+                Toast.LENGTH_SHORT).show()
+        }
+
+        Toast.makeText(context.applicationContext, "Transaksi random telah dibuat", Toast.LENGTH_SHORT).show()
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsFragment.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsFragment.kt
index b8cbc5f6ddde72493ea3dc5d9e81c53015e6557e..f928b30d81367d99044359eca8ad4d5c8754020a 100644
--- a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsFragment.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsFragment.kt
@@ -1,42 +1,61 @@
 package com.example.bondoyap.ui.transactions
 
+import android.content.pm.PackageManager
 import android.os.Bundle
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.widget.TextView
+import androidx.core.app.ActivityCompat
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.bondoyap.R
 import com.example.bondoyap.databinding.FragmentTransactionsBinding
+import com.example.bondoyap.service.api.Constants.ACTION_RANDOMIZE_TRANSACTIONS
 
-class TransactionsFragment : Fragment() {
-
+class TransactionsFragment: Fragment() {
     private var _binding: FragmentTransactionsBinding? = null
 
-    // This property is only valid between onCreateView and
-    // onDestroyView.
     private val binding get() = _binding!!
 
+    private val transactionsViewModel: TransactionsViewModel by viewModels {
+        TransactionsViewModelFactory((requireContext().applicationContext as TransactionsApplication).repository)
+    }
+
+    private val requestcode: Int = 1
+
     override fun onCreateView(
-            inflater: LayoutInflater,
-            container: ViewGroup?,
-            savedInstanceState: Bundle?
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
     ): View {
-        val transactionsViewModel =
-                ViewModelProvider(this).get(TransactionsViewModel::class.java)
-
         _binding = FragmentTransactionsBinding.inflate(inflater, container, false)
-        val root: View = binding.root
 
-        val textView: TextView = binding.textTransactions
-        transactionsViewModel.text.observe(viewLifecycleOwner) {
-            textView.text = it
+        val recyclerView = binding.root.findViewById<RecyclerView>(R.id.recyclerViewTransactions)
+        val adapter = TransactionsListAdapter(context = requireContext())
+        recyclerView.adapter = adapter
+        recyclerView.layoutManager = LinearLayoutManager(requireContext())
+
+        transactionsViewModel.allTransactions.observe(viewLifecycleOwner, Observer { transactions ->
+            transactions?.let { adapter.submitList(it) }
+        })
+
+        binding.buttonAddTransaction.setOnClickListener {
+            findNavController().navigate(R.id.navigation_add_transactions)
         }
-        return root
+
+        return binding.root
     }
 
+
     override fun onDestroyView() {
         super.onDestroyView()
         _binding = null
     }
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsListAdapter.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsListAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6c5dce13bf504632c030d4a57aa4b6fc47b3991e
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsListAdapter.kt
@@ -0,0 +1,147 @@
+package com.example.bondoyap.ui.transactions
+
+import android.content.Context
+import android.content.Intent
+import android.location.Address
+import android.location.Geocoder
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import android.widget.Toast
+import androidx.cardview.widget.CardView
+import androidx.core.content.ContextCompat.startActivity
+import androidx.navigation.Navigation
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.example.bondoyap.R
+import com.example.bondoyap.ui.transactions.data.Transactions
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.IOException
+import java.util.Locale
+
+class TransactionsListAdapter(private val context: Context) :
+    ListAdapter<Transactions, TransactionsViewHolder>(TransactionsDiffCallback()) {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder {
+        return TransactionsViewHolder.create(parent, context)
+    }
+
+    override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
+        val currentTransaction = getItem(position)
+        holder.bind(currentTransaction)
+    }
+
+}
+
+class TransactionsViewHolder(itemView: View, private val context: Context) : RecyclerView.ViewHolder(itemView) {
+    private val cardView: CardView = itemView.findViewById(R.id.cardViewTransaction)
+    private val transactionTitle: TextView = itemView.findViewById(R.id.transactionTitle)
+    private val transactionAmount: TextView = itemView.findViewById(R.id.transactionAmount)
+    private val transactionCategory: TextView = itemView.findViewById(R.id.transactionCategory)
+    private val transactionDate: TextView = itemView.findViewById(R.id.transactionDate)
+    private val transactionLocation: TextView = itemView.findViewById(R.id.transactionLocation)
+    private val geocoder: Geocoder = Geocoder(context, Locale.getDefault())
+
+    fun bind(transaction: Transactions) {
+        val maxAmountLength = 12
+        val maxTitleLength = 16
+        val maxLocationLength = 9
+
+        val amountText = if (transaction.nominal.toBigDecimal().toString().length > maxAmountLength) {
+            "IDR " + transaction.nominal.toBigDecimal().toString().substring(0, maxAmountLength) + "..."
+        } else {
+            "IDR " + transaction.nominal.toBigDecimal().toString()
+        }
+
+        val titleText = if (transaction.judul.length > maxTitleLength) {
+            transaction.judul.substring(0, maxTitleLength) + "..."
+        } else {
+            transaction.judul
+        }
+
+        transactionTitle.text = titleText
+        transactionAmount.text = amountText
+        transactionCategory.text = when (transaction.isPemasukan) {
+            true -> "Pemasukan"
+            else -> "Pengeluaran"
+        }
+
+        transactionDate.text = transaction.tanggal
+
+        if (transaction.latitude.isNotEmpty() && transaction.longitude.isNotEmpty()) {
+            CoroutineScope(Dispatchers.IO).launch {
+                try {
+                    val addresses: List<Address> = geocoder.getFromLocation(
+                        transaction.latitude.toDouble(),
+                        transaction.longitude.toDouble(),
+                        1
+                    ) ?: emptyList()
+                    if (addresses.isNotEmpty()) {
+                        val locationName = addresses[0].getAddressLine(0)
+                        Handler(Looper.getMainLooper()).post {
+                            transactionLocation.text = if (locationName.length > maxLocationLength) {
+                                locationName.substring(0, maxLocationLength) + "..."
+                            } else {
+                                locationName
+                            }
+                        }
+                    }
+                } catch (e: IOException) {
+                    e.printStackTrace()
+                }
+            }
+        } else {
+            transactionLocation.text = "Unavailable"
+        }
+
+        transactionLocation.setOnClickListener {
+            if (transactionLocation.text != "Unavailable") {
+                val mapUri = Uri.parse("https://maps.google.com/maps/search/?api=1&query=${transaction.latitude},${transaction.longitude}")
+                val intent = Intent(Intent.ACTION_VIEW, mapUri)
+                intent.setPackage("com.google.android.apps.maps")
+                if (intent.resolveActivity(context.packageManager) != null) {
+                    startActivity(context, intent, null)
+                } else {
+                    val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com/maps/search/?api=1&query=${transaction.latitude},${transaction.longitude}"))
+                    if (webIntent.resolveActivity(context.packageManager) != null) {
+                        startActivity(context, webIntent, null)
+                    } else {
+                        Toast.makeText(context, "Tidak ada app yang dapat menghandle maps", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+
+        cardView.setOnClickListener {
+            val bundle: Bundle = Bundle()
+            bundle.putInt("transaction_id", transaction.id)
+            Navigation.findNavController(itemView).navigate(R.id.navigation_edit_transactions, bundle)
+        }
+    }
+
+    companion object {
+        fun create(parent: ViewGroup, context: Context): TransactionsViewHolder {
+            val view: View = LayoutInflater.from(parent.context)
+                .inflate(R.layout.recyclerview_transactions, parent, false)
+            return TransactionsViewHolder(view, context)
+        }
+    }
+}
+
+class TransactionsDiffCallback : DiffUtil.ItemCallback<Transactions>() {
+    override fun areItemsTheSame(oldItem: Transactions, newItem: Transactions): Boolean {
+        return oldItem.id == newItem.id
+    }
+
+    override fun areContentsTheSame(oldItem: Transactions, newItem: Transactions): Boolean {
+        return oldItem == newItem
+    }
+}
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsViewModel.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsViewModel.kt
index 1a790c6f8709dc3c2f42d091b860cde6389501c3..3f5582da6b50d2bb0f8c9f03e2a5e644e783f247 100644
--- a/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsViewModel.kt
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/TransactionsViewModel.kt
@@ -1,13 +1,45 @@
 package com.example.bondoyap.ui.transactions
 
 import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import com.example.bondoyap.ui.transactions.data.Transactions
+import com.example.bondoyap.ui.transactions.data.TransactionsRepository
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
 
-class TransactionsViewModel : ViewModel() {
+class TransactionsViewModel(
+    private val repository: TransactionsRepository
+): ViewModel() {
+    val allTransactions: LiveData<List<Transactions>> = repository.allTransactions.asLiveData()
 
-    private val _text = MutableLiveData<String>().apply {
-        value = "This is Transactions Fragment"
+    fun upsert(transactions: Transactions) = viewModelScope.launch {
+        repository.upsert(transactions)
+    }
+    fun delete(transactions: Transactions) = viewModelScope.launch {
+        repository.delete(transactions)
+    }
+    suspend fun get(transactionId: Int?): Transactions {
+        val deferred: Deferred<Transactions> = viewModelScope.async {
+            repository.get(transactionId)
+        }
+        return deferred.await()
+    }
+
+    suspend fun getAllTransactionsList(): List<Transactions> {
+        return repository.getAllTransactionsList()
+    }
+}
+
+class TransactionsViewModelFactory(private val repository: TransactionsRepository): ViewModelProvider.Factory {
+    override fun<T: ViewModel> create(modelClass: Class<T>): T {
+        if (modelClass.isAssignableFrom(TransactionsViewModel::class.java)) {
+            @Suppress("UNCHECKED_CAST")
+            return TransactionsViewModel(repository) as T
+        }
+        throw IllegalArgumentException("Unknown ViewModel class")
     }
-    val text: LiveData<String> = _text
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/data/Transactions.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/data/Transactions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a5bddbd9edcc1d9bc8323ad08c4ae69d6949e37d
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/data/Transactions.kt
@@ -0,0 +1,30 @@
+package com.example.bondoyap.ui.transactions.data
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "transactions")
+data class Transactions(
+    @ColumnInfo(name = "judul")
+    val judul: String,
+
+    @ColumnInfo(name = "nominal")
+    val nominal: Double,
+
+    @ColumnInfo(name = "is_pemasukan")
+    val isPemasukan: Boolean,
+
+    @ColumnInfo(name = "tanggal")
+    val tanggal: String,
+
+    @ColumnInfo(name = "longitude")
+    var longitude: String = "",
+
+    @ColumnInfo(name = "latitude")
+    var latitude: String = "",
+
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(name = "id")
+    val id: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsDao.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsDao.kt
new file mode 100644
index 0000000000000000000000000000000000000000..00dd1fc2a43c15e7ee6ac69ca1d725245c1b554c
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsDao.kt
@@ -0,0 +1,26 @@
+package com.example.bondoyap.ui.transactions.data
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Query
+import androidx.room.Upsert
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface TransactionsDao {
+
+    @Upsert
+    suspend fun upsertTransaction(transaksi: Transactions)
+
+    @Delete
+    suspend fun deleteTransaction(transaksi: Transactions)
+
+    @Query("SELECT * FROM transactions")
+    fun getTransactions(): Flow<List<Transactions>>
+
+    @Query("SELECT * FROM transactions")
+    suspend fun getTransactionsList(): List<Transactions>
+
+    @Query("SELECT * FROM transactions WHERE transactions.id == :transactionsId")
+    suspend fun getTransactionById(transactionsId: Int?): Transactions
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsRepository.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c376411c484b935896c904d94217d7c0dc023b15
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsRepository.kt
@@ -0,0 +1,26 @@
+package com.example.bondoyap.ui.transactions.data
+
+import androidx.annotation.WorkerThread
+import kotlinx.coroutines.flow.Flow
+
+class TransactionsRepository(private val transactionsDao: TransactionsDao) {
+    val allTransactions: Flow<List<Transactions>> = transactionsDao.getTransactions()
+
+    @WorkerThread
+    suspend fun upsert(transactions: Transactions) {
+        transactionsDao.upsertTransaction(transactions)
+    }
+
+    @WorkerThread
+    suspend fun delete(transactions: Transactions) {
+        transactionsDao.deleteTransaction(transactions)
+    }
+
+    suspend fun get(transactionId: Int?): Transactions {
+        return transactionsDao.getTransactionById(transactionId)
+    }
+
+    suspend fun getAllTransactionsList(): List<Transactions> {
+        return transactionsDao.getTransactionsList()
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsRoomDatabase.kt b/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsRoomDatabase.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ada79cee07b3f11afee9b2a627791b737ab9c644
--- /dev/null
+++ b/app/src/main/java/com/example/bondoyap/ui/transactions/data/TransactionsRoomDatabase.kt
@@ -0,0 +1,70 @@
+package com.example.bondoyap.ui.transactions.data
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+@Database(
+    entities = [Transactions::class],
+    version = 3,
+    exportSchema = false
+)
+public abstract class TransactionsRoomDatabase: RoomDatabase() {
+
+    abstract fun transactionsDao(): TransactionsDao
+
+    companion object {
+        @Volatile
+        private var INSTANCE: TransactionsRoomDatabase? = null
+
+        fun getDatabase(context: Context): TransactionsRoomDatabase {
+            return INSTANCE ?: synchronized(this) {
+                val instance = Room.databaseBuilder(
+                    context.applicationContext,
+                    TransactionsRoomDatabase::class.java,
+                    "transactions_database"
+                ).addMigrations(MIGRATION_2_3).
+                build()
+                INSTANCE = instance
+                instance
+            }
+        }
+        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
+            override fun migrate(db: SupportSQLiteDatabase) {
+                db.execSQL("CREATE TABLE IF NOT EXISTS `transactions_new` " +
+                        "(`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+                        "`judul` TEXT NOT NULL, " +
+                        "`nominal` REAL NOT NULL, " +
+                        "`is_pemasukan` INTEGER NOT NULL, " +
+                        "`tanggal` TEXT NOT NULL, " +
+                        "`lokasi` TEXT NOT NULL)")
+
+                db.execSQL("INSERT INTO transactions_new (id, judul, nominal, is_pemasukan) " +
+                        "SELECT id, judul, nominal, is_pemasukan FROM transactions")
+
+                db.execSQL("DROP TABLE transactions")
+
+                db.execSQL("ALTER TABLE transactions_new RENAME TO transactions")
+            }
+        }
+        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
+            override fun migrate(db: SupportSQLiteDatabase) {
+                db.execSQL("CREATE TABLE IF NOT EXISTS `transactions_new` " +
+                        "(`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+                        "`judul` TEXT NOT NULL, " +
+                        "`nominal` REAL NOT NULL, " +
+                        "`is_pemasukan` INTEGER NOT NULL, " +
+                        "`tanggal` TEXT NOT NULL, " +
+                        "`longitude` TEXT NOT NULL, " +
+                        "`latitude` TEXT NOT NULL)")
+
+                db.execSQL("DROP TABLE transactions")
+
+                db.execSQL("ALTER TABLE transactions_new RENAME TO transactions")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_pin.xml b/app/src/main/res/drawable/ic_pin.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4304772446917f94787245ca25f0020e4a904ccd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pin.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5s-1.12,2.5 -2.5,2.5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_plus.xml b/app/src/main/res/drawable/ic_plus.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eb0f40356c67383e0760d86a3ad14bcd11220a7d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_plus.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+
+    <path
+        android:fillColor="#000"
+        android:pathData="M19,13H13v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/activity_login.xml
similarity index 81%
rename from app/src/main/res/layout/fragment_login.xml
rename to app/src/main/res/layout/activity_login.xml
index 3835370f0dd243f7d2448961484710a245cdf4d5..cf194090807d97abade273046c604fba5498297e 100644
--- a/app/src/main/res/layout/fragment_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -2,21 +2,20 @@
 <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:id="@+id/container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingLeft="@dimen/fragment_horizontal_margin"
-    android:paddingTop="@dimen/fragment_vertical_margin"
-    android:paddingRight="@dimen/fragment_horizontal_margin"
-    android:paddingBottom="@dimen/fragment_vertical_margin"
-    tools:context=".ui.login.LoginFragment">
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    tools:context=".ui.login.LoginActivity">
 
     <EditText
         android:id="@+id/email"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="24dp"
         android:layout_marginTop="96dp"
-        android:layout_marginEnd="24dp"
         android:autofillHints="@string/prompt_email"
         android:hint="@string/prompt_email"
         android:inputType="textEmailAddress"
@@ -29,9 +28,7 @@
         android:id="@+id/password"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="24dp"
         android:layout_marginTop="8dp"
-        android:layout_marginEnd="24dp"
         android:autofillHints="@string/prompt_password"
         android:hint="@string/prompt_password"
         android:imeActionLabel="@string/action_sign_in_short"
@@ -47,9 +44,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="start"
-        android:layout_marginStart="48dp"
         android:layout_marginTop="16dp"
-        android:layout_marginEnd="48dp"
         android:layout_marginBottom="64dp"
         android:enabled="false"
         android:text="@string/action_sign_in"
@@ -64,9 +59,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:layout_marginStart="32dp"
         android:layout_marginTop="64dp"
-        android:layout_marginEnd="32dp"
         android:layout_marginBottom="64dp"
         android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -74,4 +67,5 @@
         app:layout_constraintStart_toStartOf="@+id/password"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintVertical_bias="0.3" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ 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 06ea6cae22113f243efe317f984f7742418737e8..bad97d63dc8bac143e499f7ff3a349305c72700f 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -3,8 +3,7 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:paddingTop="?attr/actionBarSize">
+    android:layout_height="match_parent">
 
     <com.google.android.material.bottomnavigation.BottomNavigationView
         android:id="@+id/nav_view"
diff --git a/app/src/main/res/layout/fragment_add_transactions.xml b/app/src/main/res/layout/fragment_add_transactions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c7bc521ef2c2912a1614877a151b223a7d326f98
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_transactions.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="16dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_judul" />
+    <EditText
+        android:id="@+id/editText_judul"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:autofillHints="judul"
+        android:hint="@string/hint_judul"
+        android:inputType="text"
+        android:maxLength="150"
+        android:layout_marginBottom="16dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_nominal" />
+    <EditText
+        android:id="@+id/editText_nominal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:autofillHints="nominal"
+        android:hint="@string/hint_nominal"
+        android:inputType="numberDecimal"
+        android:maxLength="18"
+        android:layout_marginBottom="16dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_kategori" />
+    <Spinner
+        android:id="@+id/spinner_kategori"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp" />
+
+    <Button
+        android:id="@+id/button_simpan"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/button_simpan" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_edit_transactions.xml b/app/src/main/res/layout/fragment_edit_transactions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ea202d0be3584d842892ba196c00dd45967f3216
--- /dev/null
+++ b/app/src/main/res/layout/fragment_edit_transactions.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="16dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_judul" />
+    <EditText
+        android:id="@+id/editText_judul"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:autofillHints="judul"
+        android:hint="@string/hint_judul"
+        android:inputType="text"
+        android:maxLength="150"
+        android:layout_marginBottom="16dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_nominal" />
+    <EditText
+        android:id="@+id/editText_nominal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:autofillHints="nominal"
+        android:hint="@string/hint_nominal"
+        android:inputType="numberDecimal"
+        android:maxLength="18"
+        android:layout_marginBottom="16dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_kategori" />
+    <Spinner
+        android:id="@+id/spinner_kategori"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/textfield_label_lokasi" />
+    <EditText
+        android:id="@+id/editText_lokasi"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="text|textMultiLine"
+        android:gravity="top"/>
+
+    <CheckBox android:id="@+id/checkbox_update_lokasi"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Perbarui lokasi" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/button_hapus"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_marginEnd="8dp"
+            android:text="@string/button_hapus"
+            android:backgroundTint="@color/red"/>
+
+        <Button
+            android:id="@+id/button_update"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/button_update" />
+
+    </LinearLayout>
+
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_scan_result.xml b/app/src/main/res/layout/fragment_scan_result.xml
new file mode 100644
index 0000000000000000000000000000000000000000..972c342f9703685caafa1ddf6265ee9e7d1cc188
--- /dev/null
+++ b/app/src/main/res/layout/fragment_scan_result.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".ui.scanner.ScanResultFragment">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <!-- TODO: fix relative to bottom nav bar -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="72dp"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/cancel_button" />
+
+        <Button
+            android:id="@+id/save_button"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/save_button" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_scanner.xml b/app/src/main/res/layout/fragment_scanner.xml
index 883e701ac332ec99a2870cc89a74d2e6a979e81f..4caebe4c9e62ac89b85ed9966c7389d519e607b6 100644
--- a/app/src/main/res/layout/fragment_scanner.xml
+++ b/app/src/main/res/layout/fragment_scanner.xml
@@ -6,17 +6,45 @@
     android:layout_height="match_parent"
     tools:context=".ui.scanner.ScannerFragment">
 
-    <TextView
-        android:id="@+id/text_scanner"
+
+    <androidx.camera.view.PreviewView
+        android:id="@+id/preview_view"
         android:layout_width="match_parent"
+        android:layout_height="400dp"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/switch_camera_button"
+        android:layout_width="0dp"
         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"
+        android:text="@string/switch_camera_button"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toBottomOf="@id/preview_view" />
+
+    <Button
+        android:id="@+id/capture_button"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/capture_button"
+        app:layout_constraintStart_toEndOf="@+id/switch_camera_button"
+        app:layout_constraintTop_toBottomOf="@id/preview_view" />
+
+    <Button
+        android:id="@+id/gallery_button"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/gallery_button"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/capture_button"
+        app:layout_constraintTop_toBottomOf="@id/preview_view" />
+
+
+    <Button
+        android:id="@+id/upload_button"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/upload_button"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/capture_button" />
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 5f90bc6adda3319a6073dd79512e10f163e686f2..61209ef2bcd9cd0308c8511663ffedf81705ba89 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -4,7 +4,9 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".ui.settings.SettingsFragment">
+    tools:context=".ui.settings.SettingsFragment"
+    android:paddingTop="?attr/actionBarSize"
+    >
 
     <TextView
         android:id="@+id/text_settings"
@@ -15,8 +17,51 @@
         android:layout_marginEnd="8dp"
         android:textAlignment="center"
         android:textSize="20sp"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <Button
+        android:id="@+id/random_transactions"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/random_transactions"
+        app:layout_constraintTop_toBottomOf="@+id/text_settings"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp"
+        />
+
+    <Button
+        android:id="@+id/save_transactions"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/save_transactions"
+        app:layout_constraintTop_toBottomOf="@+id/random_transactions"
+        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp"
+        />
+
+    <Button
+        android:id="@+id/send_transactions"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/send_transactions"
+        app:layout_constraintTop_toBottomOf="@+id/save_transactions"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp"
+        />
+
+    <Button
+        android:id="@+id/logout_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/logout"
+        app:layout_constraintTop_toBottomOf="@+id/send_transactions"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp"/>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_transactions.xml b/app/src/main/res/layout/fragment_transactions.xml
index 45fe32c51b6329dc85f34563c32e86ecd2491f52..ad4fddd01c2a89be080305241b2b70399ef56d7a 100644
--- a/app/src/main/res/layout/fragment_transactions.xml
+++ b/app/src/main/res/layout/fragment_transactions.xml
@@ -1,22 +1,23 @@
-<?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"
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".ui.transactions.TransactionsFragment">
+    android:layout_marginTop="16dp">
 
-    <TextView
-        android:id="@+id/text_transactions"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recyclerViewTransactions"
         android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="60dp"/>
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/button_addTransaction"
+        android:layout_width="wrap_content"
         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
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginEnd="24dp"
+        android:layout_marginBottom="72dp"
+        android:src="@drawable/ic_plus"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/recyclerview_scan_result.xml b/app/src/main/res/layout/recyclerview_scan_result.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c172e9daa24a17d34ac12b0e3de9263d366866c3
--- /dev/null
+++ b/app/src/main/res/layout/recyclerview_scan_result.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/card_view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="8dp"
+    app:cardBackgroundColor="#af5eff"
+    app:cardCornerRadius="8dp"
+    app:cardElevation="4dp">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:padding="16dp">
+
+        <TextView
+            android:id="@+id/item_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="16sp" />
+
+        <TextView
+            android:id="@+id/item_quantity"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="16sp" />
+
+        <TextView
+            android:id="@+id/item_price"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="16sp" />
+    </LinearLayout>
+
+</androidx.cardview.widget.CardView>
diff --git a/app/src/main/res/layout/recyclerview_transactions.xml b/app/src/main/res/layout/recyclerview_transactions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..03524715210c6a87729033adc340fe07537e8e47
--- /dev/null
+++ b/app/src/main/res/layout/recyclerview_transactions.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/cardViewTransaction"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="8dp"
+    app:cardCornerRadius="8dp"
+    app:cardElevation="4dp"
+    android:clickable="true"
+    android:focusable="true"
+    app:cardBackgroundColor="#af5eff">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:padding="16dp">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/transactionDate"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="16sp"
+                android:text="Tanggal" />
+
+            <TextView
+                android:id="@+id/transactionTitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="20sp"
+                android:textStyle="bold"
+                android:layout_marginTop="5dp"
+                android:layout_marginBottom="5dp"
+                android:text="Judul" />
+
+            <TextView
+                android:id="@+id/transactionAmount"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="16sp"
+                android:text="Nominal" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/transactionCategory"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="16sp"
+                android:text="Kategori" />
+
+            <TextView
+                android:id="@+id/transactionLocation"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="16sp"
+                android:text="Lokasi"
+                android:layout_marginTop="36dp"
+                app:drawableLeftCompat="@drawable/ic_pin" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</androidx.cardview.widget.CardView>
diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml
index 6f3b755bf50c6b03d8714a9c6184705e6a08389f..036d09bc5fd523323794379703c4a111d1e28a04 100644
--- a/app/src/main/res/mipmap-anydpi/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
 </adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
index 6f3b755bf50c6b03d8714a9c6184705e6a08389f..036d09bc5fd523323794379703c4a111d1e28a04 100644
--- a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
 </adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..ed54b070361593fb163c9e1d1860592e25d89e3e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78ecd372343283f4157dcfd918ec5165bb3..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf1b43d672103d65381c46eb071bd1416a35b81b
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..b35925334629c8bba6dc62f069edac0463537576
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher.png b/app/src/main/res/mipmap-ldpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6a69d6f5689fdc214b28bf5fecbf33ab7f2994d
Binary files /dev/null and b/app/src/main/res/mipmap-ldpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..26e1a02af74c5fd9774f19d7b2822fd0f5908c68
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d64e58ba64d180ce43ee13bf9a17835fbca..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a14dadae65b43d5bba7e2fa2038e1e0de1b3f09
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..db1f5f9390945f1e8b0376087c007e563ddde4da
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611da081676d42f6c3f78a2c91e7bcedddedb..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..fbc6a68df058b7f7b2a65b63ccb1187b2e1852e7
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a3070fe34c611c42c0d3ad3013a0dce358be0..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..991c94a3d9a5a55d6951ebb36e1846a1571bf11d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..41fdbefc963e433891dac0a49ba1ab4ec41fbc7d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2152740a0a62351f1144cc19e7e322d7bc98ff8
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77f9f036a47549d47db79c16788749dca10..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..667709b349b96ceada152a2461ea5eb11f1a13f3
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..bbbdd222e1a09b79af898ec63e7ae778d8ec63c4
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f5083623b375139afb391af71cc533a7dd37..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..136e87df540dd155c093aaaa0c265356892b946c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d6427e6fa1074b79ccd52ef67ac15c5637e85..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..d150fa8feda7d2d53208aae66cf3cebb0843fc05
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..12c7745d046f89e7779d0eb3c4cb7354d995bfad
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae37cbc3587421d6889eadd1d91fbf1994d4..0000000000000000000000000000000000000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index efa5003d6cdb2778fc6190e8d2ef6edc22bf9d79..983df0358bc32b2943927cbc28b36223c5fe0927 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -5,11 +5,11 @@
     android:id="@+id/mobile_navigation"
     app:startDestination="@+id/navigation_transactions">
 
-    <fragment
-        android:id="@+id/navigation_login"
-        android:name="com.example.bondoyap.ui.login.LoginFragment"
-        android:label="@string/title_login"
-        tools:layout="@layout/fragment_transactions" />
+    <!--    <fragment-->
+    <!--        android:id="@+id/navigation_login"-->
+    <!--        android:name="com.example.bondoyap.ui.login.LoginActivity"-->
+    <!--        android:label="@string/title_login"-->
+    <!--        tools:layout="@layout/fragment_transactions" />-->
 
     <fragment
         android:id="@+id/navigation_transactions"
@@ -17,11 +17,35 @@
         android:label="@string/title_transactions"
         tools:layout="@layout/fragment_transactions" />
 
+    <fragment
+        android:id="@+id/navigation_add_transactions"
+        android:name="com.example.bondoyap.ui.transactions.AddTransactionsFragment"
+        android:label="@string/title_transactions"
+        tools:layout="@layout/fragment_add_transactions" />
+
+    <fragment
+        android:id="@+id/navigation_edit_transactions"
+        android:name="com.example.bondoyap.ui.transactions.EditTransactionsFragment"
+        android:label="@string/title_transactions"
+        tools:layout="@layout/fragment_edit_transactions" />
+
     <fragment
         android:id="@+id/navigation_scanner"
         android:name="com.example.bondoyap.ui.scanner.ScannerFragment"
         android:label="@string/title_scanner"
-        tools:layout="@layout/fragment_scanner" />
+        tools:layout="@layout/fragment_scanner">
+
+        <action
+            android:id="@+id/action_to_scanResultFragment"
+            app:destination="@id/navigation_scan_result" />
+
+    </fragment>
+
+    <fragment
+        android:id="@+id/navigation_scan_result"
+        android:name="com.example.bondoyap.ui.scanner.ScanResultFragment"
+        android:label="@string/title_scan_result"
+        tools:layout="@layout/fragment_scan_result" />
 
     <fragment
         android:id="@+id/navigation_graph"
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5f681ae11694396c167bee5b520246962495c34d
--- /dev/null
+++ b/app/src/main/res/values-land/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="activity_horizontal_margin">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7e065116699dfedbfa503eb28fe432bf1447dd73
--- /dev/null
+++ b/app/src/main/res/values-w1240dp/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="activity_horizontal_margin">200dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5f681ae11694396c167bee5b520246962495c34d
--- /dev/null
+++ b/app/src/main/res/values-w600dp/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="activity_horizontal_margin">48dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127d327620c93d2b2d00342a68e97b98a48d..5b69a31ff5fc8b40517d0e5624620e080be37343 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,4 +7,6 @@
     <color name="teal_700">#FF018786</color>
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
+    <color name="red">#FF0000</color>
+    <color name="ic_launcher_background">#f6bc2b</color>
 </resources>
\ 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 8676af5c24e54b33fb20416b8377c02db9e60dfe..170df652f7855ce5cc6f9f8a650e558b427c700f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4,14 +4,44 @@
     <string name="title_scanner">Scanner</string>
     <string name="title_graph">Graph</string>
     <string name="title_settings">Pengaturan</string>
-    <!-- Strings related to login -->
+    <string name="title_scan_result">Scan Result</string>
+
     <string name="title_login">Login</string>
     <string name="prompt_email">Email</string>
     <string name="prompt_password">Password</string>
     <string name="action_sign_in">Sign in</string>
     <string name="action_sign_in_short">Sign in</string>
-    <string name="welcome">"Welcome!"</string>
-    <string name="invalid_username">Not a valid email</string>
-    <string name="invalid_password">Password must be >5 characters</string>
-    <string name="login_failed">"Login failed"</string>
+    <string name="welcome">"Selamat Datang!"</string>
+    <string name="logged_in">Masuk dengan akun:</string>
+    <string name="invalid_username">Bukan email yang valid</string>
+    <string name="invalid_password">Password tidak boleh kosong</string>
+    <string name="login_failed">"Login gagal"</string>
+    <string name="logout">Logout</string>
+    <string name="title_activity_login">Login</string>
+    
+    <string name="random_transactions">Membuat transaksi random</string>
+    <string name="save_transactions">Simpan daftar transaksi</string>
+    <string name="send_transactions">Kirim daftar transaksi</string>
+
+    <string name="textfield_label_judul">Judul</string>
+    <string name="textfield_label_nominal">Nominal</string>
+    <string name="textfield_label_kategori">Kategori</string>
+    <string name="textfield_label_lokasi">Lokasi</string>
+    <string name="button_simpan">Simpan</string>
+    <string name="button_hapus">Hapus</string>
+    <string name="button_update">Update</string>
+    <string name="hint_judul">Enter Judul</string>
+    <string name="hint_nominal">Enter Nominal</string>
+    <string name="hint_kategori">Enter Kategori</string>
+    <string name="hint_lokasi">Enter Lokasi</string>
+    <string name="pemasukan">Pemasukan</string>
+    <string name="pengeluaran">Pengeluaran</string>
+    
+    <string name="capture_button">capture</string>
+    <string name="switch_camera_button">switch camera</string>
+    <string name="gallery_button">gallery</string>
+    <string name="upload_button">upload</string>
+
+    <string name="cancel_button">cancel</string>
+    <string name="save_button">save</string>
 </resources>
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 31ed43cc99341f5b95f5b15fd54f84d14a585002..96e26d4e6b245ef5a2d5b0bba7d68c7d716a61d4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,4 +2,5 @@
 plugins {
     id("com.android.application") version "8.3.0" apply false
     id("org.jetbrains.kotlin.android") version "1.9.22" apply false
+    id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false
 }
\ No newline at end of file