diff --git a/app/build.gradle.kts b/app/build.gradle.kts index badcd890cb8b664dac2c3edd8ba117ca644b62f2..1c04f7714b4f142637d9bcb6e064abcff79fd130 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,4 +66,5 @@ dependencies { implementation ("org.apache.poi:poi-ooxml:5.2.4") implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") implementation ("com.squareup.okhttp3:okhttp:4.9.1") + implementation ("com.google.android.gms:play-services-location:18.0.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e9c84d7765dd6ba21990362be955cabb8dcc23b2..cb0763c332a05756857459de3655c726e81ec2ea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ android:name="android.hardware.camera" android:required="false" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> @@ -14,6 +15,8 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> diff --git a/app/src/main/java/com/example/android_hit/DetailTransaction.kt b/app/src/main/java/com/example/android_hit/DetailTransaction.kt index faf07d21a074c8514cebfb40c0e31c00a21ed00b..dfefc31f4d864fc79dd8201a594c83947ec6e600 100644 --- a/app/src/main/java/com/example/android_hit/DetailTransaction.kt +++ b/app/src/main/java/com/example/android_hit/DetailTransaction.kt @@ -1,33 +1,55 @@ package com.example.android_hit +import android.Manifest import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.IntentSender +import android.content.pm.PackageManager +import android.location.Geocoder +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import com.google.android.gms.location.LocationRequest 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.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.example.android_hit.databinding.FragmentDetailTransactionBinding import com.example.android_hit.room.TransactionDB import com.example.android_hit.room.TransactionEntity +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.ResolvableApiException +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.LocationSettingsStatusCodes import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -class DetailTransaction : Fragment() { +class DetailTransaction : Fragment(), LocationListener { private var _binding: FragmentDetailTransactionBinding? = null private val binding get() = _binding!! - + private lateinit var fusedLocationProviderClient: FusedLocationProviderClient private lateinit var database: TransactionDB private var category: String = "" + private var coordinate: String = "-6.927314530264154, 107.77007155415649" + private var location: String = "" private lateinit var transactionReceiver: TransactionReceiver private val RANDOMIZE_ACTION = "com.example.android_hit.RANDOMIZE_ACTION" + private lateinit var locationManager: LocationManager + private lateinit var locationRequest: LocationRequest + + companion object { var fragmentCounter = 0 var amountInput = "" @@ -64,16 +86,39 @@ class DetailTransaction : Fragment() { val filter = IntentFilter(RANDOMIZE_ACTION) LocalBroadcastManager.getInstance(requireContext()).registerReceiver(transactionReceiver, filter) + locationManager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager + fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(requireContext()) + binding.inputLocation.setOnClickListener { + Log.d("Location", "Input location clicked") + checkLocationPermission() + } + binding.radioExpense.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { category = "Expense" + binding.radioExpense.setTextColor(ContextCompat.getColor(requireContext(), R.color.secondary4)) + } else { + binding.radioExpense.setTextColor( + ContextCompat.getColor( + requireContext(), + R.color.primary_color_1 + ) + ) } } binding.radioIncome.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { category = "Income" + binding.radioIncome.setTextColor(ContextCompat.getColor(requireContext(), R.color.secondary5)) + } else { + binding.radioIncome.setTextColor( + ContextCompat.getColor( + requireContext(), + R.color.primary_color_1 + ) + ) } } @@ -101,7 +146,9 @@ class DetailTransaction : Fragment() { binding.buttonSave.setOnClickListener { val title = binding.inputTitle.text.toString() val amount = binding.inputAmount.text.toString() - val location = binding.inputLocation.text.toString() + location = binding.inputLocation.text.toString() + Log.d("koordinat", "Coordinate: $coordinate") + Log.d("lokasi", "Location: $location") if (title.isNotEmpty() && amount.isNotEmpty() && location.isNotEmpty() && (binding.radioExpense.isChecked || binding.radioIncome.isChecked)) { try { @@ -111,10 +158,8 @@ class DetailTransaction : Fragment() { val id = intent.getInt("id", 0) val transaction = database.transactionDao.getId(id) - timestamp = transaction?.timestamp - } + timestamp = transaction.timestamp - if (intent != null) { database.transactionDao.updateTransaction( TransactionEntity( intent.getInt("id", 0), @@ -122,6 +167,7 @@ class DetailTransaction : Fragment() { amount.toInt(), category, location, + coordinate, timestamp.toString() ) ) @@ -140,6 +186,7 @@ class DetailTransaction : Fragment() { amountValue, category, location, + coordinate, currentDateAndTime ) ) @@ -178,7 +225,102 @@ class DetailTransaction : Fragment() { } } + private fun checkLocationPermission() { + if(ActivityCompat.checkSelfPermission( + requireContext(), + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + Log.d("Location", "Permission granted") + checkGPS() + } else { + Log.d("Location", "Requesting location permission") + ActivityCompat.requestPermissions( + requireActivity(), + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + 44 + ) + } + } + + private fun getUserLocation() { + if (ActivityCompat.checkSelfPermission( + requireContext(), + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( + requireContext(), + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return + } + fusedLocationProviderClient.lastLocation.addOnSuccessListener { task -> + + val location = task + + if (location != null) { + try { + val geocoder = Geocoder(requireContext(), Locale.getDefault()) + val address = geocoder.getFromLocation(location.latitude, location.longitude, 1) + val addressLine = address?.get(0)?.getAddressLine(0) + + coordinate = "${location.latitude},${location.longitude}" + binding.inputLocation.setText(addressLine) + + Log.d("Location", "Address: $addressLine") + Log.d("Location", "Coordinate: $coordinate") + + } catch (e: Exception) { + e.printStackTrace() + + } + } + } + + } + + private fun checkGPS() { + locationRequest = LocationRequest.create() + locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY + locationRequest.interval = 10000 + locationRequest.fastestInterval = 5000 + val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest) + builder.setAlwaysShow(true) + val result = LocationServices.getSettingsClient(requireContext().applicationContext).checkLocationSettings(builder.build()) + result.addOnCompleteListener { task -> + try { + var response = task.getResult( + ApiException::class.java + ) + getUserLocation() + } catch (ex: ApiException) { + when (ex.statusCode) { + LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> { + try { + val resolvableApiException = ex as ResolvableApiException + resolvableApiException.startResolutionForResult( + this@DetailTransaction.requireActivity(), + 200 + ) + } catch (_: IntentSender.SendIntentException) { + } + } + LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { + Unit + } + else -> {} + } + } + } + } override fun onDestroyView() { super.onDestroyView() @@ -187,6 +329,9 @@ class DetailTransaction : Fragment() { if(fragmentCounter>1){ LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(transactionReceiver) } + } + override fun onLocationChanged(location: Location) { + TODO("Not yet implemented") } } diff --git a/app/src/main/java/com/example/android_hit/Scan.kt b/app/src/main/java/com/example/android_hit/Scan.kt index addd17e967a5fadd8743102c2312c5ab64aa5356..e7d9bf304f975064da5dc47bff5fd093bfca4d07 100644 --- a/app/src/main/java/com/example/android_hit/Scan.kt +++ b/app/src/main/java/com/example/android_hit/Scan.kt @@ -215,6 +215,7 @@ class Scan : Fragment() { amount = amount.toInt(), category = "Expense", location = "Location", // Replace with actual location + coordinate = "0,0", // Replace with actual coordinate timestamp = System.currentTimeMillis().toString() // Replace with actual timestamp ) diff --git a/app/src/main/java/com/example/android_hit/Transaction.kt b/app/src/main/java/com/example/android_hit/Transaction.kt index ee32b6d9397ed1c1096cd8a2c9997f2fd7fca007..3658c4469e2dd5d926e5ca722d920cc448ac43c4 100644 --- a/app/src/main/java/com/example/android_hit/Transaction.kt +++ b/app/src/main/java/com/example/android_hit/Transaction.kt @@ -28,7 +28,6 @@ private const val ARG_PARAM2 = "param2" * create an instance of this fragment. */ class Transaction : Fragment() { - // TODO: Rename and change types of parameters private lateinit var binding: FragmentTransactionBinding private lateinit var recyclerView: RecyclerView private lateinit var adapter: TransactionAdapter @@ -36,10 +35,6 @@ class Transaction : Fragment() { private lateinit var fab: FloatingActionButton private var list = mutableListOf<TransactionEntity>() -// override fun onCreate(savedInstanceState: Bundle?) { -// super.onCreate(savedInstanceState) -// } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -52,11 +47,14 @@ class Transaction : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView = binding.rvTransaction + fab = binding.fabAdd + recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.addItemDecoration( DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL) ) + adapter = TransactionAdapter(list) recyclerView.adapter = adapter fab.setOnClickListener { @@ -68,15 +66,13 @@ class Transaction : Fragment() { deleteTransaction(position) } }) + database = TransactionDB.getInstance(requireContext()) getData() - val totalExpenseAmount = database.transactionDao.getTotalExpense() - binding.amountExpense.text = totalExpenseAmount.toString() } private fun deleteTransaction(position: Int) { val deletedItem = list[position] - // Kurangi total expense amount jika transaksi yang dihapus memiliki kategori expense if (deletedItem.category == "Expense") { val expenseAmount = deletedItem.amount val currentTotalExpense = database.transactionDao.getTotalExpense() @@ -88,21 +84,25 @@ class Transaction : Fragment() { val newTotalIncome = currentTotalIncome - incomeAmount binding.amountIncome.text = newTotalIncome.toString() } - // Hapus transaksi dari database dan daftar transaksi database.transactionDao.deleteTransaction(deletedItem) list.removeAt(position) adapter.notifyItemRemoved(position) + getIncomeExpense() + } + + private fun getIncomeExpense() { + val currencyFormat = NumberFormat.getCurrencyInstance(Locale("id", "ID")) + val totalExpenseAmount = database.transactionDao.getTotalExpense() + val totalIncomeAmount = database.transactionDao.getTotalIncome() + binding.amountExpense.text = currencyFormat.format(totalExpenseAmount) + binding.amountIncome.text = currencyFormat.format(totalIncomeAmount) } private fun getData() { list.clear() list.addAll(database.transactionDao.getAllTransaction()) adapter.notifyDataSetChanged() - val totalExpenseAmount = database.transactionDao.getTotalExpense() - val currencyFormat = NumberFormat.getCurrencyInstance(Locale("id", "ID")) - binding.amountExpense.text = currencyFormat.format(totalExpenseAmount) - val totalIncomeAmount = database.transactionDao.getTotalIncome() - binding.amountIncome.text = currencyFormat.format(totalIncomeAmount) + getIncomeExpense() } override fun onResume() { diff --git a/app/src/main/java/com/example/android_hit/adapter/TransactionAdapter.kt b/app/src/main/java/com/example/android_hit/adapter/TransactionAdapter.kt index e6bb02d00c71d2a87fac4fbdf408c4eb3a66349f..222f10956ca194d13832072cfb32cb64a24a5b59 100644 --- a/app/src/main/java/com/example/android_hit/adapter/TransactionAdapter.kt +++ b/app/src/main/java/com/example/android_hit/adapter/TransactionAdapter.kt @@ -4,11 +4,15 @@ import android.content.Intent import android.net.Uri import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.startActivity import androidx.recyclerview.widget.RecyclerView import com.example.android_hit.DetailTransactionActivity +import com.example.android_hit.R import com.example.android_hit.databinding.RowTransactionBinding import com.example.android_hit.room.TransactionEntity +import java.text.NumberFormat +import java.util.Locale class TransactionAdapter(private val list: MutableList<TransactionEntity>) : RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder>() { @@ -16,14 +20,17 @@ class TransactionAdapter(private val list: MutableList<TransactionEntity>) : Rec fun bind(transaction: TransactionEntity) { binding.apply { title.text = transaction.title - amount.text = transaction.amount.toString() + val currencyFormat = NumberFormat.getCurrencyInstance(Locale("id", "ID")) + amount.text = currencyFormat.format(transaction.amount) category.text = transaction.category location.text = transaction.location date.text = transaction.timestamp + val colorId = if (transaction.category == "Expense") R.color.secondary4 else R.color.secondary5 + category.setTextColor(ContextCompat.getColor(binding.root.context, colorId)) + deleteButton.setOnClickListener { - val position = adapterPosition - onDeleteClickListener?.onDeleteClick(position) + onDeleteClickListener?.onDeleteClick(adapterPosition) } editButton.setOnClickListener { @@ -33,7 +40,11 @@ class TransactionAdapter(private val list: MutableList<TransactionEntity>) : Rec } location.setOnClickListener { - val locationUri = "geo:0,0?q=${transaction.location}" + val locationUri = if (transaction.coordinate != "-6.927314530264154, 107.77007155415649") { + "geo:0,0?q=${transaction.location}" + } else { + "geo:0,0?q=${transaction.coordinate}" + } val mapIntent = Intent(Intent.ACTION_VIEW, Uri.parse(locationUri)) mapIntent.setPackage("com.google.android.apps.maps") startActivity(binding.root.context, mapIntent, null) diff --git a/app/src/main/java/com/example/android_hit/room/TransactionDB.kt b/app/src/main/java/com/example/android_hit/room/TransactionDB.kt index a7e316cb86bf70b7608f53c85a8268c49d82adbe..172598b297ef53f3a8a3111265ccb9490b2abc52 100644 --- a/app/src/main/java/com/example/android_hit/room/TransactionDB.kt +++ b/app/src/main/java/com/example/android_hit/room/TransactionDB.kt @@ -7,7 +7,7 @@ import androidx.room.RoomDatabase @Database( entities = [TransactionEntity::class], - version = 5, + version = 6, exportSchema = false ) abstract class TransactionDB : RoomDatabase() { diff --git a/app/src/main/java/com/example/android_hit/room/TransactionEntity.kt b/app/src/main/java/com/example/android_hit/room/TransactionEntity.kt index 4fb6391189fb4799ecb1bcb0af7b032d900649b8..7ece1a50b79e2d6230856e000cdd28deb8cfd39b 100644 --- a/app/src/main/java/com/example/android_hit/room/TransactionEntity.kt +++ b/app/src/main/java/com/example/android_hit/room/TransactionEntity.kt @@ -11,5 +11,6 @@ class TransactionEntity ( @ColumnInfo(name = "amount") val amount : Int, @ColumnInfo(name = "category") val category : String, @ColumnInfo(name = "location") val location : String, + @ColumnInfo(name = "coordinate") val coordinate: String, @ColumnInfo(name = "timestamp") val timestamp: String ) \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_detail_transaction.xml b/app/src/main/res/layout-land/fragment_detail_transaction.xml index 429f1adc556073db835e06ba712cec7206a94112..9bed128a3bcfbbe8e9452dbc3a2730297114f2de 100644 --- a/app/src/main/res/layout-land/fragment_detail_transaction.xml +++ b/app/src/main/res/layout-land/fragment_detail_transaction.xml @@ -81,7 +81,8 @@ android:layout_height="wrap_content" android:layout_marginRight="24dp" android:text="Income" - android:textSize="24sp" /> + android:textSize="24sp" + android:textColor="@color/primary_color_1"/> <RadioButton android:id="@+id/radioExpense" @@ -89,7 +90,8 @@ android:layout_height="wrap_content" android:layout_marginLeft="24dp" android:text="Expense" - android:textSize="24sp" /> + android:textSize="24sp" + android:textColor="@color/primary_color_1"/> </RadioGroup> <TextView diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 90dbd53823483052f25e69e5a6abad51fd915e19..64d347f1366a83a93c4102473ef0c8b7dfe28e5d 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -63,7 +63,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/login" - android:background="@color/primary1" + android:backgroundTint="@color/primary_color_1" android:textColor="@color/white" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/fragment_detail_transaction.xml b/app/src/main/res/layout/fragment_detail_transaction.xml index 348b0f88eed34695cad30ff900794141e339d0a0..617269914565b8649c9ccf8f94730c254d2e4f7c 100644 --- a/app/src/main/res/layout/fragment_detail_transaction.xml +++ b/app/src/main/res/layout/fragment_detail_transaction.xml @@ -53,7 +53,8 @@ android:layout_height="wrap_content" android:inputType="textPersonName" android:padding="8dp" - android:textSize="36sp" /> + android:textSize="36sp" + android:gravity="end"/> </com.google.android.material.textfield.TextInputLayout> <TextView @@ -77,7 +78,7 @@ android:layout_height="wrap_content" android:layout_marginRight="24dp" android:text="Income" - android:textSize="24sp" /> + android:textSize="24sp"/> <RadioButton android:id="@+id/radioExpense" @@ -85,7 +86,7 @@ android:layout_height="wrap_content" android:layout_marginLeft="24dp" android:text="Expense" - android:textSize="24sp" /> + android:textSize="24sp"/> </RadioGroup> <TextView diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index 136ab06e71b214b0baf00c8e414734c3877925e2..f8b28837e6bcf4e9f82edbabf8b70a7fd6c6ca59 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -95,7 +95,7 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="@string/login" - android:background="@color/primary1" + android:backgroundTint="@color/primary_color_1" android:textColor="@color/white" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index fd194862f96cc5afabca08f3b27f813053db3a6f..ec35c5ced0b731b0b4bc9a239f3bf3bb7d930ff5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,4 +15,5 @@ <color name="secondary2">#47455B</color> <color name="secondary3">#095BFA</color> <color name="secondary4">#D65C5C</color> + <color name="secondary5">#2DA039</color> </resources> \ No newline at end of file