diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 900d2e8f894749fd25899ee3ddb01fae98732cb1..7420bfc9ccb5ff852b1842a15e8c4b52382da24d 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("kotlin-kapt") } android { @@ -46,9 +47,12 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("org.apache.poi:poi:5.2.4") implementation("org.apache.poi:poi-ooxml:5.2.4") + implementation("com.google.android.gms:play-services-location:21.2.0") testImplementation("junit:junit:4.13.2") implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + implementation("org.greenrobot:eventbus:3.2.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") @@ -59,6 +63,9 @@ dependencies { // coroutines implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + implementation("androidx.room:room-runtime:2.6.1") + annotationProcessor("androidx.room:room-compiler:2.6.1") + kapt("androidx.room:room-compiler:2.6.1") // graph implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c778eb5f0a0b044c21bf4b0f0907f6e1530260cf..1e82142e39492f97568fb785adf3dcd83cd95e69 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,11 +6,16 @@ android:name="android.hardware.camera" android:required="false" /> + + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> - <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" @@ -21,10 +26,13 @@ android:supportsRtl="true" android:theme="@style/Theme.Bondoman" tools:targetApi="31"> + <activity + android:name=".ContainerActivity" + android:exported="false" /> <activity android:name=".LoginActivity" android:exported="false" - android:screenOrientation="portrait"/> + android:screenOrientation="portrait" /> <activity android:name=".MainActivity" android:exported="false" /> @@ -37,9 +45,6 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <activity android:name=".ScanActivity" - android:exported="false" - android:screenOrientation="portrait"/> <service android:name=".services.JWTExpiry" android:enabled="true" @@ -52,5 +57,14 @@ <action android:name="com.example.bondoman.services.RandomizeTransaction" /> </intent-filter> </receiver> + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="com.github.mikephil.charting.provider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/Adapter.kt b/app/src/main/java/com/example/bondoman/Adapter.kt deleted file mode 100644 index 8992a8cbe902871787cb758cbc3b1ff13eeade9c..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/Adapter.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.bondoman - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.recyclerview.widget.RecyclerView -import com.example.bondoman.retrofit.data.Transaction - -class TransactionAdapter(private val listData: ArrayList<Transaction>): RecyclerView.Adapter<TransactionAdapter.DataViewHolder>(){ - - override fun onBindViewHolder(holder: DataViewHolder, position: Int) { - val currentData = listData[position] - holder.txtName.text = currentData.name - holder.txtCategory.text = currentData.category - val temp = "IDR " + currentData.price.toString() - holder.txtPrice.text = temp - holder.txtLocation.text = currentData.location - holder.txtDate.text = currentData.date - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataViewHolder { - val view: View = LayoutInflater.from(parent.context).inflate(R.layout.list_transaction,parent,false) - return DataViewHolder(view) - } - - override fun getItemCount(): Int { - return listData.count() - } - class DataViewHolder(item: View) : RecyclerView.ViewHolder(item) { - val txtName: TextView = item.findViewById(R.id.itemName) - val txtCategory: TextView = item.findViewById(R.id.category) - val txtDate: TextView = item.findViewById(R.id.itemDate) - val txtPrice : TextView = item.findViewById(R.id.itemPrice) - val txtLocation : TextView = item.findViewById(R.id.itemLocation) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/AddTransactionFragment.kt b/app/src/main/java/com/example/bondoman/AddTransactionFragment.kt index 8529fe2af352a3079d49a5ceff5a2e873e9aa6c3..a46671920081d581e5b7d4b6d8b39385539c2288 100644 --- a/app/src/main/java/com/example/bondoman/AddTransactionFragment.kt +++ b/app/src/main/java/com/example/bondoman/AddTransactionFragment.kt @@ -1,22 +1,196 @@ package com.example.bondoman +import android.content.pm.PackageManager import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -class AddTransactionFragment : Fragment() { +import com.example.bondoman.databinding.FragmentAddTransactionBinding +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import android.location.Geocoder +import android.util.Log +import android.widget.Toast +import androidx.core.content.ContextCompat +import java.util.Locale +import android.widget.ArrayAdapter +import android.widget.Spinner +import androidx.core.app.ActivityCompat +import androidx.lifecycle.ViewModelProvider +import com.example.bondoman.services.RandomTransactionEvent +import com.example.bondoman.services.RandomizeTransaction +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + + +class AddTransactionFragment : Fragment(){ + private lateinit var binding: FragmentAddTransactionBinding + private lateinit var geocoder: Geocoder + private lateinit var fusedLocationClient: FusedLocationProviderClient + private var address : String? = null + private var isFetched : Boolean = false + private lateinit var db: DBViewModel + + private var pendingTransaction: MutableList<Pair<String, Double>> = mutableListOf() + + companion object { + const val LOCATION_PERMISSION_REQUEST_CODE = 1 + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_add_transaction, container, false) + ): View { + binding = FragmentAddTransactionBinding.inflate(inflater, container, false) + geocoder = Geocoder(requireContext(), Locale.getDefault()) + fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()) + db = ViewModelProvider(requireActivity())[DBViewModel::class.java] + + if (pendingTransaction.isNotEmpty()){ + pendingTransaction.forEach { pair -> + db.addTransaksi(pair.first, "Pengeluaran", pair.second, "Unknown") + } + pendingTransaction.clear() + } + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val categorySpinner: Spinner = binding.addCategoryField + val categories = resources.getStringArray(R.array.category_array) + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, categories) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + categorySpinner.adapter = adapter + + if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + Log.e("fetchLocation", "Both fine and coarse location permissions granted") + fetchLocation { fetchedAddress -> + address = fetchedAddress + } + } else { + Log.e("fetchLocation", "Requesting permissions for fine and coarse location") + ActivityCompat.requestPermissions(requireActivity(), arrayOf( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_COARSE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE) + } + + binding.addButton.setOnClickListener { + fetchLocation { fetchedAddress -> + address = fetchedAddress + } + // Retrieve values from other fields + val name = binding.addNameField.text.toString() + val amountText = binding.addPriceField.text.toString() + // Retrieve selected category from the Spinner + val category = categorySpinner.selectedItem.toString() + + Log.d("addTransactionWoi", "Button clicked") + + + val amount: Double + try { + amount = amountText.toDouble() + } catch (e: NumberFormatException) { + Toast.makeText(requireContext(), "Amount must be a valid number", Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + + + val location = if (address != null) { + address!! + } else { + binding.addLocationField.text.toString() + } + + Log.d("addTransactionWoi", "the location is $location") + + db.addTransaksi(name, category, amount, location) + requireActivity().onBackPressed() + } + + } + override fun onDestroyView() { + super.onDestroyView() + EventBus.getDefault().unregister(this) + } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onRandomTransactionReceived(event: com.example.bondoman.services.RandomTransactionEvent) { + val transactionName = event.transactionName + val price = event.price + pendingTransaction.add(Pair(transactionName, price)) + } + + private fun fetchLocation(callback: (String) -> Unit) { + var address = "Unknown" + + if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + fusedLocationClient.lastLocation.addOnSuccessListener { location -> + location?.let { + Log.e("fetchLocation", "Fetching location") + val latitude = location.latitude + val longitude = location.longitude + address = getAddress(latitude, longitude) + callback(address) + } + }.addOnFailureListener { e -> + Log.e("fetchLocation", "Failed to fetch location: ${e.message}") + Toast.makeText(requireContext(), "Failed to fetch location", Toast.LENGTH_SHORT).show() + callback(address) + } + } else { + ActivityCompat.requestPermissions(requireActivity(), arrayOf( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_COARSE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE) + callback(address) + } + } + + + + private fun getAddress(latitude: Double, longitude: Double): String { + val addresses = geocoder.getFromLocation(latitude, longitude, 1) + return if (!addresses.isNullOrEmpty()) { + val address = addresses[0] + "${address.getAddressLine(0)}, ${address.locality}, ${address.adminArea}, ${address.countryName}" + } else { + "Unknown Location" + } + } + override fun onRequestPermissionsResult(requestCode: Int, + permissions: Array<String>, grantResults: IntArray) { + when (requestCode) { + LOCATION_PERMISSION_REQUEST_CODE -> { + // If request is cancelled, the result arrays are empty. + if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { + // permission was granted, yay! Do the + // location-related task you need to do. + fetchLocation { fetchedAddress -> + address = fetchedAddress + } + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + Log.e("fetchLocation", "Location permissions denied") + } + return + } + // Add other 'when' lines to check for other + // permissions this app might request. + else -> { + // Ignore all other requests. + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ContainerActivity.kt b/app/src/main/java/com/example/bondoman/ContainerActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e9d9a5aa36d07ff7cba4e5b69ff0c75131b1970 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ContainerActivity.kt @@ -0,0 +1,38 @@ +package com.example.bondoman + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat + +class ContainerActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_container) + + if (savedInstanceState == null) { + // Check if intent contains transaction ID + val transactionId = intent.getIntExtra("id", -1) + if (transactionId != -1) { +// Start the EditTransactionFragment + val fragment = EditTransactionFragment.newInstance(transactionId) + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, fragment) + .commit() + + } else { + // Start the AddTransactionFragment + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, AddTransactionFragment()) + .commit() + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container) + fragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/DBViewModel.kt b/app/src/main/java/com/example/bondoman/DBViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..481c682d27b86eba1e640d8644fe67f6915017e2 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/DBViewModel.kt @@ -0,0 +1,47 @@ +package com.example.bondoman + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.example.bondoman.models.GraphData +import com.example.bondoman.retrofit.data.TransactionDB +import com.example.bondoman.retrofit.data.entity.Category +import com.example.bondoman.retrofit.data.entity.TransactionEntity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class DBViewModel(application: Application) : AndroidViewModel(application) { + private val db = TransactionDB.getInstance(application) + private val viewModelScope = CoroutineScope(Dispatchers.IO) + + fun addTransaksi(name: String, category: String, price: Double, location: String = "") { + viewModelScope.launch { + val currentDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + var tempLocation = location + val newTransaction = TransactionEntity( + name = name, + category = Category.valueOf(category), + date = currentDate, + price = price, + location = tempLocation + ) + db.transactionDao().insertAll(newTransaction) + } + } + + fun getGraphData(): LiveData<List<GraphData>> { + val data = MutableLiveData<List<GraphData>>() + viewModelScope.launch { + val dbData = db.transactionDao().sumPriceByCategory() + data.postValue(dbData) + } + return data + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/DetailTransactionFragment.kt b/app/src/main/java/com/example/bondoman/DetailTransactionFragment.kt deleted file mode 100644 index 736ebcd5eaba016113a65ab3efd7816d74492c3a..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/DetailTransactionFragment.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.bondoman - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.example.bondoman.databinding.FragmentDetailTransactionBinding -import com.example.bondoman.databinding.FragmentEditTransactionBinding - -class DetailTransactionFragment : Fragment() { - private lateinit var binding : FragmentDetailTransactionBinding - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = FragmentDetailTransactionBinding.inflate(inflater, container, false) - return binding.root - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/EditTransactionFragment.kt b/app/src/main/java/com/example/bondoman/EditTransactionFragment.kt index f05307faecb115a672226f7f67c31b065178aabf..85565e358756e2a0459bf38ca254c1b7679d9c8a 100644 --- a/app/src/main/java/com/example/bondoman/EditTransactionFragment.kt +++ b/app/src/main/java/com/example/bondoman/EditTransactionFragment.kt @@ -1,14 +1,37 @@ package com.example.bondoman +import android.app.AlertDialog +import android.content.pm.PackageManager import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.example.bondoman.AddTransactionFragment.Companion.LOCATION_PERMISSION_REQUEST_CODE import com.example.bondoman.databinding.FragmentEditTransactionBinding +import com.example.bondoman.retrofit.adapter.TransactionAdapter +import com.example.bondoman.retrofit.data.TransactionDB +import com.example.bondoman.retrofit.data.entity.TransactionEntity class EditTransactionFragment : Fragment() { private lateinit var binding : FragmentEditTransactionBinding + private lateinit var db : TransactionDB + private lateinit var adapter : TransactionAdapter + private val listTransaction = ArrayList<TransactionEntity>() + companion object { + private const val ARG_TRANSACTION_ID = "id" + + fun newInstance(id: Int): EditTransactionFragment { + val fragment = EditTransactionFragment() + val args = Bundle() + args.putInt(ARG_TRANSACTION_ID, id) + fragment.arguments = args + return fragment + } + } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -19,6 +42,93 @@ class EditTransactionFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val id = arguments?.getInt(ARG_TRANSACTION_ID) ?: -1 + db = TransactionDB.getInstance(requireContext()) + fetchTransactionDetails(id) + + binding.deleteButton.setOnClickListener { + AlertDialog.Builder(requireContext()) + .setTitle("Delete Transaction") + .setMessage("Are you sure you want to delete this transaction?") + .setPositiveButton("Yes") { _, _ -> + val transaction = db.transactionDao().getId(id) + db.transactionDao().delete(transaction) + requireActivity().onBackPressed() + } + .setNegativeButton("No") { _, _ -> } + .show() + } + + if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + Log.e("fetchLocation", "Both fine and coarse location permissions granted") + fetchLocation { fetchedAddress -> + binding.locationField.setText(fetchedAddress) + } + } else { + Log.e("fetchLocation", "Requesting permissions for fine and coarse location") + ActivityCompat.requestPermissions(requireActivity(), arrayOf( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_COARSE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE) + } + + binding.saveButton.setOnClickListener { + db = TransactionDB.getInstance(requireContext()) + val kategori = db.transactionDao().getId(id).category + val date = db.transactionDao().getId(id).date + val nama = binding.nameField.text.toString() + val nominal = binding.priceField.text.toString().toDouble() + val lokasi = binding.locationField.text.toString() + + val newTransaction = TransactionEntity( + id = id, + name = nama, + price = nominal, + location = lokasi, + category = kategori, + date = date + ) + db.transactionDao().update(newTransaction) + + requireActivity().onBackPressed() + } + } + + private fun fetchTransactionDetails(id: Int) { + val transaction = db.transactionDao().getId(id) + binding.nameField.setText(transaction.name) + binding.priceField.setText(transaction.price.toString()) + binding.locationField.setText(transaction.location) + binding.dateField.setText(transaction.date) + binding.categoryField.setText(transaction.category.toString()) + } + private fun fetchLocation(callback: (String) -> Unit) { + var address = "Unknown" + } + override fun onRequestPermissionsResult(requestCode: Int, + permissions: Array<String>, grantResults: IntArray) { + when (requestCode) { + LOCATION_PERMISSION_REQUEST_CODE -> { + // If request is cancelled, the result arrays are empty. + if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { + // permission was granted, yay! Do the + // location-related task you need to do. + fetchLocation { fetchedAddress -> + binding.locationField.setText(fetchedAddress) + } + } else { + // permission denied, boo! Disable the + // functionality that depends on this permission. + Log.e("fetchLocation", "Location permissions denied") + } + return + } + // Add other 'when' lines to check for other + // permissions this app might request. + else -> { + // Ignore all other requests. + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/GraphFragment.kt b/app/src/main/java/com/example/bondoman/GraphFragment.kt index 39756ce0696417c9e03e1b6b92a31fec426ba7d0..fc5449ccfd5b6618ae45e66ee3fb3f21c5f61967 100644 --- a/app/src/main/java/com/example/bondoman/GraphFragment.kt +++ b/app/src/main/java/com/example/bondoman/GraphFragment.kt @@ -1,10 +1,22 @@ package com.example.bondoman +import android.content.res.Configuration +import android.graphics.Color import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.AnimationUtils +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import com.example.bondoman.retrofit.data.TransactionDB import com.github.mikephil.charting.charts.PieChart import com.github.mikephil.charting.data.PieData import com.github.mikephil.charting.data.PieDataSet @@ -12,45 +24,73 @@ import com.github.mikephil.charting.data.PieEntry class GraphFragment : Fragment() { + private lateinit var db: DBViewModel + private lateinit var database: TransactionDB override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + database = TransactionDB.getInstance(requireContext()) + if (!database.isOpen) { + database.openHelper.writableDatabase + } val view = inflater.inflate(R.layout.fragment_grafik, container, false) val pieChart: PieChart = view.findViewById(R.id.pieChart) + db = ViewModelProvider(requireActivity())[DBViewModel::class.java] + + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + navbar.setBackgroundResource(R.drawable.navbar_background) + toolbar.setBackgroundColor( + Color.parseColor("#000113") + ) + val textView = toolbar.findViewById<TextView>(R.id.toolbar_text) + val transactionButton = requireActivity().findViewById<ImageButton>(R.id.transaction_button) + val graphButton = requireActivity().findViewById<ImageButton>(R.id.graph_button) + val settingButton = requireActivity().findViewById<ImageButton>(R.id.setting_button) + textView.text = "Grafik" + textView.setTextColor(Color.WHITE) + toolbar.findViewById<ImageButton>(R.id.toolbar_back_button).setImageResource(R.drawable.ic_arrow_left_white) + transactionButton.isSelected = false + graphButton.isSelected = true + settingButton.isSelected = false // Dummy data - val income = 5000f - val outcome = 3000f + val graphData = db.getGraphData() + Log.d("DATA", graphData.toString()) - val entries = ArrayList<PieEntry>() - entries.add(PieEntry(income, "Income")) - entries.add(PieEntry(outcome, "Outcome")) + db.getGraphData().observe(viewLifecycleOwner, Observer { graphData -> + val entries = ArrayList<PieEntry>() + for (data in graphData) { + entries.add(PieEntry(data.amount!!.toFloat(), data.category.toString())) + } - val dataSet = PieDataSet(entries, "Transaction Summary") + val dataSet = PieDataSet(entries, "Transaction Summary") + val colors = ArrayList<Int>() + colors.add(resources.getColor(R.color.colorIncome)) + colors.add(resources.getColor(R.color.colorOutcome)) + dataSet.colors = colors + dataSet.valueTextSize = 24f + dataSet.valueTextColor = resources.getColor(R.color.text_graph) - val colors = ArrayList<Int>() - colors.add(resources.getColor(R.color.colorIncome)) - colors.add(resources.getColor(R.color.colorOutcome)) - dataSet.colors = colors - dataSet.valueTextSize = 24f - dataSet.valueTextColor = resources.getColor(R.color.text_graph) + val data = PieData(dataSet) + pieChart.data = data - val data = PieData(dataSet) + pieChart.description.isEnabled = false - pieChart.data = data + pieChart.setHoleColor(android.R.color.transparent) - pieChart.description.isEnabled = false + val legend = pieChart.legend + legend.isEnabled = false - pieChart.setHoleColor(android.R.color.transparent) + pieChart.invalidate() + }) - val legend = pieChart.legend - legend.isEnabled = false - - pieChart.invalidate() return view } + + } diff --git a/app/src/main/java/com/example/bondoman/LoginActivity.kt b/app/src/main/java/com/example/bondoman/LoginActivity.kt index 46560ff0b1ee0e10f6a3565a609e9f614f22602b..f7bf5ed918a19c98abf89248fe3ccb537d01a2ea 100644 --- a/app/src/main/java/com/example/bondoman/LoginActivity.kt +++ b/app/src/main/java/com/example/bondoman/LoginActivity.kt @@ -9,30 +9,56 @@ import android.content.Intent import android.widget.Toast import android.annotation.SuppressLint import android.util.Log +import android.view.View +import android.widget.RelativeLayout +import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import com.example.bondoman.models.LoginRequest +import com.example.bondoman.services.ConnectivityObserver +import com.example.bondoman.services.NetworkSensing import com.example.bondoman.utils.RetrofitInstance import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.json.JSONException import org.json.JSONObject class LoginActivity : AppCompatActivity() { + private lateinit var networkSensing: NetworkSensing @SuppressLint("CommitPrefEdits") -// @OptIn(DelicateCoroutinesApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) - val buttonLogin = findViewById<Button>(R.id.buttonLogin) -// val editTextEmail = findViewById<EditText>(R.id.emailAddress) -// val editTextPassword = findViewById<EditText>(R.id.password) + val editTextEmail = findViewById<EditText>(R.id.emailAddress) + val editTextPassword = findViewById<EditText>(R.id.password) + val connectionLost = findViewById<RelativeLayout>(R.id.conn_lost) + networkSensing = NetworkSensing(this) + networkSensing.observe() + .onEach { state -> + when (state) { + ConnectivityObserver.NetworkState.CONNECTED -> { + editTextEmail.visibility = View.VISIBLE + editTextPassword.visibility = View.VISIBLE + buttonLogin.visibility = View.VISIBLE + connectionLost.visibility = View.GONE + } + ConnectivityObserver.NetworkState.DISCONNECTED -> { + editTextEmail.visibility = View.GONE + editTextPassword.visibility = View.GONE + buttonLogin.visibility = View.GONE + connectionLost.visibility = View.VISIBLE + } + } + } + .launchIn(lifecycleScope) buttonLogin.setOnClickListener { // val email = editTextEmail.text.toString() // val password = editTextPassword.text.toString() - val email = "13521010@std.stei.itb.ac.id" - val password = "password_13521010" + val email = "13521003@std.stei.itb.ac.id" + val password = "password_13521003" CoroutineScope(Dispatchers.IO).launch { try { @@ -43,6 +69,7 @@ class LoginActivity : AppCompatActivity() { val editor = sharedPreferences.edit() editor.apply { putString("TOKEN", token) + putString("EMAIL", email) }.apply() val intent = Intent(this@LoginActivity, MainActivity::class.java) startActivity(intent) diff --git a/app/src/main/java/com/example/bondoman/LogoutFragment.kt b/app/src/main/java/com/example/bondoman/LogoutFragment.kt deleted file mode 100644 index 2861f8e5d055e30495ee65df4e2fc49882b0fa6e..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/LogoutFragment.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.example.bondoman -import android.content.Context.MODE_PRIVATE -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import androidx.fragment.app.Fragment -import android.content.Intent -import android.widget.EditText -import com.example.bondoman.services.RandomizeTransaction - -class LogoutFragment : Fragment() { - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_logout, container, false) - - view.findViewById<Button>(R.id.logoutButton).setOnClickListener { - logout() - } - - view.findViewById<Button>(R.id.randomButton).setOnClickListener { - val listRandomText = listOf("MacBook Pro", "MacBook Air", "Mac Mini", "Mac Pro", "iMac") - val randomText = listRandomText.random() - - val randomizeTransactionIntent = Intent(requireContext(), RandomizeTransaction::class.java) - randomizeTransactionIntent.putExtra("transactionName", randomText) - randomizeTransactionIntent.setAction("com.example.bondoman.services.RandomizeTransaction") - requireContext().sendBroadcast(randomizeTransactionIntent) - } - return view - } - - private fun logout() { - clearUserSession() - val loginIntent = Intent(requireContext(), LoginActivity::class.java) - loginIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(loginIntent) - } - - private fun clearUserSession() { - val sharedPreferences = requireContext().getSharedPreferences("sharedPrefs", MODE_PRIVATE) - - val editor = sharedPreferences.edit() - editor.clear() - editor.apply() - } - - fun updateEditText(randomText: String?) { - view?.findViewById<EditText>(R.id.transactionName)?.setText(randomText) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/MainActivity.kt b/app/src/main/java/com/example/bondoman/MainActivity.kt index ab741fc399f6308aeaa8de8ef8c2cb46c8a59431..026b0b5c225ab54b797f02d3f81d7807067fb9c0 100644 --- a/app/src/main/java/com/example/bondoman/MainActivity.kt +++ b/app/src/main/java/com/example/bondoman/MainActivity.kt @@ -1,98 +1,138 @@ package com.example.bondoman import android.content.Intent -import androidx.appcompat.app.AppCompatActivity +import android.graphics.Rect import android.os.Bundle +import android.util.Log +import android.view.MenuItem import android.view.View import android.widget.ImageButton -import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.app.NavUtils import androidx.core.content.ContextCompat -import androidx.lifecycle.lifecycleScope -import com.example.bondoman.services.JWTExpiry +import androidx.fragment.app.FragmentManager +import androidx.navigation.NavController +import androidx.navigation.Navigation import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.setupActionBarWithNavController +import com.example.bondoman.services.JWTExpiry +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContentProviderCompat.requireContext +import androidx.lifecycle.lifecycleScope +import com.example.bondoman.retrofit.data.TransactionDB +import com.example.bondoman.services.ConnectivityObserver import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import com.example.bondoman.services.NetworkSensing +import org.greenrobot.eventbus.EventBus class MainActivity : AppCompatActivity() { private lateinit var service: Intent - private lateinit var networkSensing: NetworkSensing + private lateinit var transactionButton: ImageButton + private lateinit var graphButton: ImageButton + private lateinit var settingButton: ImageButton + private lateinit var scanButton: ImageButton + private lateinit var navController: NavController + private lateinit var database: TransactionDB override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) service = Intent(this, JWTExpiry::class.java) startService(service) - networkSensing = NetworkSensing(this) -// supportFragmentManager.beginTransaction() -// .replace(R.id.fragment_container, LogoutFragment()) -// .commit() + + database = TransactionDB.getInstance(this) + if (!database.isOpen) { + database.openHelper.writableDatabase + } + + EventBus.getDefault().register(TransactionFragment()) + Log.d("EventBus", "Registered fragment as subscriber") val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val toolbarButton = findViewById<ImageButton>(R.id.toolbar_back_button) + navController = navHostFragment.navController + val fragmentManager: FragmentManager = supportFragmentManager - // Get the NavController from the NavHostFragment val navController = navHostFragment.navController - val transactionButton = findViewById<ImageButton>(R.id.transaction_button) + transactionButton = findViewById(R.id.transaction_button) transactionButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.navbar_transaction_selector)) transactionButton.isSelected = true - val graphButton = findViewById<ImageButton>(R.id.graph_button) + graphButton = findViewById(R.id.graph_button) graphButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.navbar_graph_selector)) - val settingButton = findViewById<ImageButton>(R.id.setting_button) + settingButton = findViewById(R.id.setting_button) settingButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.navbar_setting_selector)) - val scanButton = findViewById<ImageButton>(R.id.scan_button) - - networkSensing.observe() - .onEach { state -> - when (state) { - ConnectivityObserver.NetworkState.CONNECTED -> { - Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show() - } - ConnectivityObserver.NetworkState.DISCONNECTED -> { - Toast.makeText(this, "Disconnected", Toast.LENGTH_SHORT).show() - } - } - } - .launchIn(lifecycleScope) + scanButton = findViewById(R.id.scan_button) + toolbarButton.setOnClickListener { + navController.navigateUp() + } transactionButton.setOnClickListener { - transactionButton.isSelected = true - graphButton.isSelected = false - settingButton.isSelected = false - navController.navigate(R.id.transaction_fragment) + navigateTo(R.id.transaction_fragment) } graphButton.setOnClickListener { - transactionButton.isSelected = false - graphButton.isSelected = true - settingButton.isSelected = false - navController.navigate(R.id.graph_fragment) + navigateTo(R.id.graph_fragment) } settingButton.setOnClickListener { - transactionButton.isSelected = false - graphButton.isSelected = false - settingButton.isSelected = true - navController.navigate(R.id.setting_fragment) + navigateTo(R.id.setting_fragment) } scanButton.setOnClickListener { - val intent = Intent(this, ScanActivity::class.java) - startActivity(intent) + navigateTo(R.id.scan_fragment) } + val viewTreeObserver = window.decorView.viewTreeObserver + viewTreeObserver.addOnGlobalLayoutListener { + val r = Rect() + window.decorView.getWindowVisibleDisplayFrame(r) + val screenHeight = window.decorView.rootView.height + val keypadHeight = screenHeight - r.bottom + + if (keypadHeight > screenHeight * 0.15) { + val navbar = findViewById<ConstraintLayout>(R.id.navigation_bar) + navbar.visibility = View.GONE + } else { + val navbar = findViewById<ConstraintLayout>(R.id.navigation_bar) + navbar.visibility = View.VISIBLE + } + } } override fun onSupportNavigateUp(): Boolean { val navController = findNavController(R.id.nav_host_fragment) return navController.navigateUp() || super.onSupportNavigateUp() } + private fun navigateTo(id: Int) { + transactionButton.isSelected = (id == R.id.transaction_fragment) + graphButton.isSelected = (id == R.id.graph_fragment) + settingButton.isSelected = (id == R.id.setting_fragment) + navController.navigate(id) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + // Respond to the action bar's Up/Home button + NavUtils.navigateUpFromSameTask(this) + true + } + else -> super.onOptionsItemSelected(item) + } + } + override fun onDestroy() { super.onDestroy() + database.close() stopService(service) + EventBus.getDefault().unregister(AddTransactionFragment()) + } + } diff --git a/app/src/main/java/com/example/bondoman/ScanActivity.kt b/app/src/main/java/com/example/bondoman/ScanActivity.kt deleted file mode 100644 index f539a6385b7a57c989b0e348298b31195810a47f..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/ScanActivity.kt +++ /dev/null @@ -1,346 +0,0 @@ -package com.example.bondoman - -import android.Manifest -import android.content.ContentValues.TAG -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.ImageFormat -import android.graphics.SurfaceTexture -import android.hardware.camera2.CameraCaptureSession -import android.hardware.camera2.CameraDevice -import android.hardware.camera2.CameraManager -import android.hardware.camera2.CaptureRequest -import android.hardware.camera2.TotalCaptureResult -import android.hardware.camera2.params.OutputConfiguration -import android.hardware.camera2.params.SessionConfiguration -import android.media.ImageReader -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.os.Handler -import android.os.HandlerThread -import android.util.Log -import android.view.Surface -import android.view.TextureView -import android.view.View -import android.widget.ImageButton -import android.widget.ImageView -import androidx.activity.ComponentActivity -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.app.ActivityCompat -import com.example.bondoman.models.ScanRequest -import com.example.bondoman.utils.RetrofitInstance -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import okhttp3.MediaType -import okhttp3.MultipartBody -import okhttp3.RequestBody -import retrofit2.HttpException -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.util.concurrent.Executors - -class ScanActivity : ComponentActivity() { - lateinit var capReq: CaptureRequest.Builder - private lateinit var handler: Handler - private lateinit var handlerThread: HandlerThread - private lateinit var cameraManager: CameraManager - lateinit var textureView: TextureView - lateinit var cameraCaptureSession: CameraCaptureSession - lateinit var cameraDevice: CameraDevice - lateinit var imageReader: ImageReader - - private lateinit var captureButton: ImageButton - private lateinit var recaptureButton: ImageButton - private lateinit var galleryButton: ImageButton - private lateinit var confirmButton: ImageButton - - private lateinit var imageFile: File - - private var job: Job? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_scan) - captureButton = findViewById(R.id.captureButton) - recaptureButton = findViewById(R.id.recaptureButton) - galleryButton = findViewById(R.id.galleryButton) - confirmButton = findViewById(R.id.confirmButton) - requestPermissionLauncher.launch(CAMERA_PERMISSION) - - } - - override fun onDestroy() { - super.onDestroy() - cameraDevice.close() - handler.removeCallbacksAndMessages(null) - handlerThread.quitSafely() - textureView.surfaceTexture?.release() - textureView.surfaceTextureListener = null - - } - - private fun configureCameraPreview() { - // Create a capture request for preview - - - // Create a new camera capture session for the preview - capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) - val surface = Surface(textureView.surfaceTexture) - capReq.addTarget(surface) - val outputConfigurationSurface = OutputConfiguration(surface) - val outputConfigurationImageReader = OutputConfiguration(imageReader.surface) - val sessionConfiguration = SessionConfiguration( - SessionConfiguration.SESSION_REGULAR, - listOf(outputConfigurationSurface, outputConfigurationImageReader), - Executors.newSingleThreadExecutor(), - object : CameraCaptureSession.StateCallback() { - override fun onConfigured(session: CameraCaptureSession) { - cameraCaptureSession = session - cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) - } - - override fun onConfigureFailed(session: CameraCaptureSession) { - // Handle configuration failure - } - } - ) - - cameraDevice.createCaptureSession(sessionConfiguration) - - } - - private fun displayCapturedImage() { - cameraCaptureSession.stopRepeating() - - // Retrieve the captured image data from your ImageReader - val image = imageReader.acquireLatestImage() - val buffer = image.planes[0].buffer - val bytes = ByteArray(buffer.remaining()) - buffer.get(bytes) - val bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) - - // Save the image as a File -// val file = File(getExternalFilesDir(null), "selectedImage.jpg") -// file.writeBytes(bytes) -// imageFile = file // Assuming imageFile is a class attribute - // Display the captured image on your TextureView - runOnUiThread { - textureView.surfaceTexture?.let { _ -> - val canvas = textureView.lockCanvas() - if (canvas != null) { - canvas.drawBitmap(bitmapImage, 0f, 0f, null) - textureView.unlockCanvasAndPost(canvas) - } else { - Log.e(TAG, "canvas error") - } - } - } - // Close and release the Image when done - image.close() - } - - private fun displayUploadedImage(uri: Uri) { - captureButton.visibility = View.INVISIBLE - galleryButton.visibility = View.INVISIBLE - recaptureButton.visibility = View.VISIBLE - confirmButton.visibility = View.VISIBLE - val imageView = findViewById<ImageView>(R.id.imageView) - imageView.setImageURI(uri) - imageView.visibility = View.VISIBLE - textureView.visibility = View.GONE - -// val parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r") -// val fileDescriptor = parcelFileDescriptor?.fileDescriptor -// val file = File(cacheDir, "MyImage.jpg") -// val inputStream = FileInputStream(fileDescriptor) -// val outputStream = FileOutputStream(file) -// inputStream.copyTo(outputStream) -// imageFile = file // Assuming imageFile is a class attribute - - } - - private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> - if (uri != null) { - displayUploadedImage(uri) - } - } - private fun openCamera(){ - - if (ActivityCompat.checkSelfPermission( - this, - Manifest.permission.CAMERA - ) != PackageManager.PERMISSION_GRANTED - ) { - ActivityCompat.requestPermissions( - this, CAMERA_PERMISSION, 0 - ) - return - } - cameraManager.openCamera(cameraManager.cameraIdList[0], object : CameraDevice.StateCallback() { - override fun onOpened(camera: CameraDevice) { - cameraDevice = camera - capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) - val surface = Surface(textureView.surfaceTexture) - capReq.addTarget(surface) - val outputConfigurationSurface = OutputConfiguration(surface) - val outputConfigurationImageReader = OutputConfiguration(imageReader.surface) - val sessionConfiguration = SessionConfiguration( - SessionConfiguration.SESSION_REGULAR, - listOf(outputConfigurationSurface, outputConfigurationImageReader), - Executors.newSingleThreadExecutor(), - object : CameraCaptureSession.StateCallback() { - override fun onConfigured(session: CameraCaptureSession) { - cameraCaptureSession = session - cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) - } - - override fun onConfigureFailed(session: CameraCaptureSession) { - // Handle configuration failure - } - } - ) - - cameraDevice.createCaptureSession(sessionConfiguration) - } - - override fun onDisconnected(camera: CameraDevice) { - cameraDevice.close() - } - - override fun onError(camera: CameraDevice, error: Int) { - cameraDevice.close() - Log.e(TAG, "Camera error: $error") - } - - }, handler) - } - - private val requestPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> - permissions.forEach { (_, isGranted) -> - if (isGranted) { - textureView = findViewById(R.id.textureView) - cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager - handlerThread = HandlerThread("videoThread") - handlerThread.start() - handler = Handler(handlerThread.looper) - - textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener { - override fun onSurfaceTextureAvailable( - surface: SurfaceTexture, - width: Int, - height: Int - ) { - openCamera() - } - - override fun onSurfaceTextureSizeChanged( - surface: SurfaceTexture, - width: Int, - height: Int - ) { - } - - override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { - - return false - } - - override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { - } - - } - - imageReader = ImageReader.newInstance(1080, 1323, ImageFormat.JPEG, 1) - - captureButton.setOnClickListener { - captureButton.visibility = View.INVISIBLE - galleryButton.visibility = View.INVISIBLE - recaptureButton.visibility = View.VISIBLE - confirmButton.visibility = View.VISIBLE - capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) - capReq.addTarget(imageReader.surface) - cameraCaptureSession.capture(capReq.build(), object : CameraCaptureSession.CaptureCallback() { - override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { - super.onCaptureCompleted(session, request, result) - displayCapturedImage() - } - }, null) - } - - - recaptureButton.setOnClickListener { - captureButton.visibility = View.VISIBLE - galleryButton.visibility = View.VISIBLE - recaptureButton.visibility = View.INVISIBLE - confirmButton.visibility = View.INVISIBLE - textureView.visibility = View.VISIBLE - findViewById<ImageView>(R.id.imageView).visibility = View.GONE - configureCameraPreview() - } - - - galleryButton.setOnClickListener { - getContent.launch("image/*") - } - - confirmButton.setOnClickListener { - job = CoroutineScope(Dispatchers.IO).launch { - val sharedPreferences = getSharedPreferences("sharedPrefs", Context.MODE_PRIVATE) - val token = sharedPreferences.getString("TOKEN", "") ?: "" - try { - val requestFile = RequestBody.create( - MediaType.parse("image/jpg"), - imageFile - ) - val body = MultipartBody.Part.createFormData("file", imageFile.name, requestFile) - val response = RetrofitInstance.api.uploadFile(token, body) // replace "your_token" with the actual token - if (response.isSuccessful) { - // Get the response body - val responseBody = response.body() - - // Convert the response body to a string and print it - Log.d(TAG, responseBody.toString()) - } else { - // Handle the error response - println("Error: ${response.errorBody()?.string()}") - } - } catch (e: HttpException) { - Log.e(TAG, e.message()) - } catch (e: Throwable) { - Log.e(TAG, e.stackTraceToString()) - } - - } - } - - } else { - val intent = Intent(this, MainActivity::class.java) - startActivity(intent) - } - } - } - - companion object { - private val CAMERA_PERMISSION = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - arrayOf( - Manifest.permission.CAMERA, - Manifest.permission.READ_MEDIA_IMAGES, - ) - } else { - arrayOf( - Manifest.permission.CAMERA, - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ScanFragment.kt b/app/src/main/java/com/example/bondoman/ScanFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..cca6b5e589e08f7dde91ac09a26923b70b1b3981 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ScanFragment.kt @@ -0,0 +1,487 @@ +package com.example.bondoman + +import android.Manifest +import android.app.AlertDialog +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.ImageFormat +import android.graphics.SurfaceTexture +import android.hardware.camera2.CameraCaptureSession +import android.hardware.camera2.CameraDevice +import android.hardware.camera2.CameraManager +import android.hardware.camera2.CaptureRequest +import android.hardware.camera2.TotalCaptureResult +import android.hardware.camera2.params.OutputConfiguration +import android.hardware.camera2.params.SessionConfiguration +import android.media.ImageReader +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import android.os.ParcelFileDescriptor +import android.util.Log +import android.view.LayoutInflater +import android.view.Surface +import android.view.TextureView +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.example.bondoman.services.ConnectivityObserver +import com.example.bondoman.services.NetworkSensing +import com.example.bondoman.utils.RetrofitInstance +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody +import retrofit2.HttpException +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.util.concurrent.Executors + + +class ScanFragment : Fragment() { + private lateinit var handler: Handler + private lateinit var handlerThread: HandlerThread + private lateinit var cameraManager: CameraManager + lateinit var capReq: CaptureRequest.Builder + lateinit var textureView: TextureView + lateinit var cameraCaptureSession: CameraCaptureSession + lateinit var imageReader: ImageReader + + private lateinit var captureButton: ImageButton + private lateinit var recaptureButton: ImageButton + private lateinit var galleryButton: ImageButton + private lateinit var confirmButton: ImageButton + private lateinit var db: DBViewModel + + private lateinit var imageFile: File + + private var job: Job? = null + private var cameraDevice: CameraDevice? = null + private lateinit var networkSensing: NetworkSensing + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_scan, container, false) + captureButton = view.findViewById(R.id.captureButton) + recaptureButton = view.findViewById(R.id.recaptureButton) + galleryButton = view.findViewById(R.id.galleryButton) + confirmButton = view.findViewById(R.id.confirmButton) + + db = ViewModelProvider(requireActivity())[DBViewModel::class.java] + + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + toolbar.setBackgroundColor(Color.parseColor("#1B1A55")) + navbar.setBackgroundResource(R.drawable.navbar_bordered_background) + val textView = toolbar.findViewById<TextView>(R.id.toolbar_text) + val transactionButton = requireActivity().findViewById<ImageButton>(R.id.transaction_button) + val graphButton = requireActivity().findViewById<ImageButton>(R.id.graph_button) + val settingButton = requireActivity().findViewById<ImageButton>(R.id.setting_button) + textView.text = "Scan Nota" + textView.setTextColor(Color.WHITE) + toolbar.findViewById<ImageButton>(R.id.toolbar_back_button).setImageResource(R.drawable.ic_arrow_left_white) + transactionButton.isSelected = false + graphButton.isSelected = false + settingButton.isSelected = false + + val frameLayoutScan = view.findViewById<FrameLayout>(R.id.FrameLayout) + val imageConn = view.findViewById<ImageView>(R.id.image_conn) + val textConn = view.findViewById<TextView>(R.id.title_conn) + val descConn = view.findViewById<TextView>(R.id.message_conn) + val scanFragment = view.findViewById<RelativeLayout>(R.id.scan_fragment) + + networkSensing = NetworkSensing(requireContext()) + networkSensing.observe() + .onEach { state -> + when (state) { + ConnectivityObserver.NetworkState.CONNECTED -> { + imageConn.visibility = View.GONE + textConn.visibility = View.GONE + descConn.visibility = View.GONE + frameLayoutScan.visibility = View.VISIBLE + galleryButton.visibility = View.VISIBLE + captureButton.visibility = View.VISIBLE + scanFragment.setBackgroundColor(Color.parseColor("#1B1A55")) + } + ConnectivityObserver.NetworkState.DISCONNECTED -> { + imageConn.visibility = View.VISIBLE + textConn.visibility = View.VISIBLE + descConn.visibility = View.VISIBLE + frameLayoutScan.visibility = View.GONE + galleryButton.visibility = View.GONE + captureButton.visibility = View.GONE + scanFragment.setBackgroundColor(resources.getColor(R.color.white)) + } + } + } + .launchIn(lifecycleScope) + + return view + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestPermissionLauncher.launch(CAMERA_PERMISSION) + + } + + override fun onDestroy() { + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + + toolbar.setBackgroundColor( + ContextCompat.getColor( + requireContext(), + android.R.color.transparent + ) + ) + navbar.setBackgroundResource(R.drawable.navbar_background) + super.onDestroy() + cameraDevice?.close() + handler.removeCallbacksAndMessages(null) + handlerThread.quitSafely() + textureView.surfaceTexture?.release() + textureView.surfaceTextureListener = null + + } + + private fun configureCameraPreview() { + capReq = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) + val surface = Surface(textureView.surfaceTexture) + capReq.addTarget(surface) + val outputConfigurationSurface = OutputConfiguration(surface) + val outputConfigurationImageReader = OutputConfiguration(imageReader.surface) + val sessionConfiguration = SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + listOf(outputConfigurationSurface, outputConfigurationImageReader), + Executors.newSingleThreadExecutor(), + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(session: CameraCaptureSession) { + cameraCaptureSession = session + cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) + } + + override fun onConfigureFailed(session: CameraCaptureSession) { + + } + } + ) + + cameraDevice!!.createCaptureSession(sessionConfiguration) + + } + + private fun displayCapturedImage() { + cameraCaptureSession.stopRepeating() + + val image = imageReader.acquireLatestImage() + val buffer = image.planes[0].buffer + val bytes = ByteArray(buffer.remaining()) + buffer.get(bytes) + val bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + + val file = File(requireActivity().getExternalFilesDir(null), "selectedImage.jpg") + file.writeBytes(bytes) + imageFile = file + + requireActivity().runOnUiThread { + textureView.surfaceTexture?.let { _ -> + val canvas = textureView.lockCanvas() + if (canvas != null) { + canvas.drawBitmap(bitmapImage, 0f, 0f, null) + textureView.unlockCanvasAndPost(canvas) + } + } + } + image.close() + } + + private fun displayUploadedImage(uri: Uri) { + captureButton.visibility = View.INVISIBLE + galleryButton.visibility = View.INVISIBLE + recaptureButton.visibility = View.VISIBLE + confirmButton.visibility = View.VISIBLE + val imageView = view?.findViewById<ImageView>(R.id.imageView) + if (imageView != null) { + imageView.setImageURI(uri) + imageView.visibility = View.VISIBLE + textureView.visibility = View.GONE + } + + var parcelFileDescriptor: ParcelFileDescriptor? = null + try { + parcelFileDescriptor = requireActivity().contentResolver.openFileDescriptor(uri, "r") + val fileDescriptor = parcelFileDescriptor?.fileDescriptor + val file = File(requireActivity().cacheDir, "MyImage.jpg") + val inputStream = FileInputStream(fileDescriptor) + val outputStream = FileOutputStream(file) + inputStream.copyTo(outputStream) + imageFile = file + } catch (e: Exception) { + Log.e(ContentValues.TAG, e.message.toString()) + } finally { + try { + parcelFileDescriptor?.close() + } catch (e: IOException) { + Log.e(ContentValues.TAG, e.message.toString()) + } + } + + } + + private fun createPopUp(title: String, message: String) { + if (isAdded) { + val dialogBuilder = AlertDialog.Builder(requireActivity()) + dialogBuilder.setTitle(title) + dialogBuilder.setMessage(message) + + dialogBuilder.setPositiveButton("Ok") { _, _ -> + findNavController().navigate(R.id.transaction_fragment) + } + requireActivity().runOnUiThread { + dialogBuilder.show() + } + } + } + + private val getContent = + registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> + if (uri != null) { + displayUploadedImage(uri) + } + } + + private fun openCamera() { + + if (ActivityCompat.checkSelfPermission( + requireActivity(), + Manifest.permission.CAMERA + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + requireActivity(), CAMERA_PERMISSION, 0 + ) + return + } + cameraManager.openCamera( + cameraManager.cameraIdList[0], + object : CameraDevice.StateCallback() { + override fun onOpened(camera: CameraDevice) { + cameraDevice = camera + capReq = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) + val surface = Surface(textureView.surfaceTexture) + capReq.addTarget(surface) + val outputConfigurationSurface = OutputConfiguration(surface) + val outputConfigurationImageReader = OutputConfiguration(imageReader.surface) + val sessionConfiguration = SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + listOf(outputConfigurationSurface, outputConfigurationImageReader), + Executors.newSingleThreadExecutor(), + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(session: CameraCaptureSession) { + cameraCaptureSession = session + cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null) + } + + override fun onConfigureFailed(session: CameraCaptureSession) { + + } + } + ) + + cameraDevice!!.createCaptureSession(sessionConfiguration) + } + + override fun onDisconnected(camera: CameraDevice) { + cameraDevice?.close() + } + + override fun onError(camera: CameraDevice, error: Int) { + cameraDevice?.close() + Log.e(ContentValues.TAG, "Camera error: $error") + } + + }, + handler + ) + } + + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> + permissions.forEach { (_, isGranted) -> + if (isGranted) { + textureView = view?.findViewById(R.id.textureView)!! + cameraManager = + requireActivity().getSystemService(Context.CAMERA_SERVICE) as CameraManager + handlerThread = HandlerThread("videoThread") + handlerThread.start() + handler = Handler(handlerThread.looper) + + textureView.surfaceTextureListener = + object : TextureView.SurfaceTextureListener { + override fun onSurfaceTextureAvailable( + surface: SurfaceTexture, + width: Int, + height: Int + ) { + openCamera() + } + + override fun onSurfaceTextureSizeChanged( + surface: SurfaceTexture, + width: Int, + height: Int + ) { + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + + return false + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { + } + + } + + imageReader = ImageReader.newInstance(1080, 1323, ImageFormat.JPEG, 1) + + captureButton.setOnClickListener { + captureButton.visibility = View.INVISIBLE + galleryButton.visibility = View.INVISIBLE + recaptureButton.visibility = View.VISIBLE + confirmButton.visibility = View.VISIBLE + capReq = + cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) + capReq.addTarget(imageReader.surface) + cameraCaptureSession.capture( + capReq.build(), + object : CameraCaptureSession.CaptureCallback() { + override fun onCaptureCompleted( + session: CameraCaptureSession, + request: CaptureRequest, + result: TotalCaptureResult + ) { + super.onCaptureCompleted(session, request, result) + displayCapturedImage() + } + }, + null + ) + } + + + recaptureButton.setOnClickListener { + captureButton.visibility = View.VISIBLE + galleryButton.visibility = View.VISIBLE + recaptureButton.visibility = View.INVISIBLE + confirmButton.visibility = View.INVISIBLE + textureView.visibility = View.VISIBLE + requireView().findViewById<ImageView>(R.id.imageView).visibility = View.GONE + configureCameraPreview() + } + + + galleryButton.setOnClickListener { + getContent.launch("image/*") + } + + confirmButton.setOnClickListener { + println("UDAH DIKLIK") + job = CoroutineScope(Dispatchers.IO).launch { + val sharedPreferences = + requireActivity().getSharedPreferences( + "sharedPrefs", + Context.MODE_PRIVATE + ) + val token = sharedPreferences.getString("TOKEN", "") ?: "" + try { + val requestFile = RequestBody.create( + MediaType.parse("image/jpg"), + imageFile + ) + val body = MultipartBody.Part.createFormData( + "file", + imageFile.name, + requestFile + ) + val response = + RetrofitInstance.api.uploadBill("Bearer $token", body) + println("Bearer $token") + if (response.isSuccessful) { + val responseBody = response.body() + val items = responseBody?.items + items?.items?.forEach { + db.addTransaksi(it.name, "Pengeluaran", it.qty*it.price, "Unknown") + } + createPopUp( + "Berhasil", + "Berhasil menambahkan transaksi" + ) + } else { + createPopUp( + "Gagal", + "Nota gagal dibaca. Error: ${ + response.errorBody()?.toString() + }" + ) + } + } catch (e: HttpException) { + Log.e(ContentValues.TAG, e.message()) + } catch (e: Throwable) { + Log.e(ContentValues.TAG, e.stackTraceToString()) + } + + } + } + + } else { + val intent = Intent(requireActivity(), MainActivity::class.java) + startActivity(intent) + } + } + } + + + companion object { + private val CAMERA_PERMISSION = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.READ_MEDIA_IMAGES, + ) + } else { + arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ScannerFragment.kt b/app/src/main/java/com/example/bondoman/ScannerFragment.kt deleted file mode 100644 index a6163caa4d7348e6c452a17aa2a504ce45d717d6..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/ScannerFragment.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.bondoman - -import android.app.Activity -import android.os.Bundle -import android.view.LayoutInflater -import android.content.Intent -import android.graphics.Bitmap -import android.provider.MediaStore -import androidx.fragment.app.Fragment -import android.view.View -import android.view.ViewGroup - -class ScannerFragment : Fragment() { - val REQUEST_IMAGE_CAPTURE = 1 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - dispatchTakePictureIntent() - } - - private fun dispatchTakePictureIntent() { - Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> - takePictureIntent.resolveActivity(requireActivity().packageManager)?.also { - startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) { - val imageBitmap = data?.extras?.get("data") as Bitmap - // Handle the bitmap - } - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_scanner, container, false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/SettingFragment.kt b/app/src/main/java/com/example/bondoman/SettingFragment.kt index 84a8b4f65fd57a88006748174aedc7dc7922eb5d..f532d8087e88bd6889fdd044ec3f711f63fb1b46 100644 --- a/app/src/main/java/com/example/bondoman/SettingFragment.kt +++ b/app/src/main/java/com/example/bondoman/SettingFragment.kt @@ -1,22 +1,38 @@ package com.example.bondoman import android.app.AlertDialog import android.content.ActivityNotFoundException +import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.Intent +import android.graphics.Color +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.example.bondoman.databinding.FragmentSettingBinding import com.example.bondoman.helper.Xls -import com.example.bondoman.models.SqlTransaction -import com.example.bondoman.sql.TransactionSQL +import com.example.bondoman.retrofit.data.TransactionDB +import com.example.bondoman.services.RandomizeTransaction +import com.github.mikephil.charting.BuildConfig +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import kotlin.random.Random class SettingFragment: Fragment() { private lateinit var binding : FragmentSettingBinding + private lateinit var database: TransactionDB private var savedFilePath: String? = null // private lateinit var sendEmailButton: Button @@ -25,16 +41,26 @@ class SettingFragment: Fragment() { savedInstanceState: Bundle? ): View? { val navController = findNavController() - // Inflate the layout for this fragment -// val view = inflater.inflate(R.layout.fragment_setting, container, false) -// sendEmailButton = view.findViewById(R.id.sendEmailButton) -// -// // Set OnClickListener -// sendEmailButton.setOnClickListener { -// sendEmail() -// } -// -// return view + database = TransactionDB.getInstance(requireContext()) + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + navbar.setBackgroundResource(R.drawable.navbar_background) + toolbar.setBackgroundColor( + ContextCompat.getColor( + requireContext(), + R.color.broken_white + ) + ) + val textView = toolbar.findViewById<TextView>(R.id.toolbar_text) + val transactionButton = requireActivity().findViewById<ImageButton>(R.id.transaction_button) + val graphButton = requireActivity().findViewById<ImageButton>(R.id.graph_button) + val settingButton = requireActivity().findViewById<ImageButton>(R.id.setting_button) + textView.text = "Pengaturan" + textView.setTextColor(Color.BLACK) + toolbar.findViewById<ImageButton>(R.id.toolbar_back_button).setImageResource(R.drawable.ic_arrow_left_black) + transactionButton.isSelected = false + graphButton.isSelected = false + settingButton.isSelected = true binding = FragmentSettingBinding.inflate(inflater, container, false) return binding.root @@ -43,21 +69,8 @@ class SettingFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.saveButton.setOnClickListener{ - - // Retrieve transactions from the database - val dbTransaction = TransactionSQL(requireContext()) - dbTransaction.open() - val listTransaction = dbTransaction.findAll() - - if (listTransaction.isNotEmpty()) { - // If there are transactions, show the format selection dialog - showFormatSelectionDialog(listTransaction) - } else { - // Handle case when there are no transactions in the database - Toast.makeText(requireContext(), "No transactions found in the database", Toast.LENGTH_SHORT).show() - } + saveToXls() } - binding.sendEmailButton.setOnClickListener { sendEmail() } @@ -66,57 +79,61 @@ class SettingFragment: Fragment() { logout() } binding.randomizeButton.setOnClickListener { - sendEmail() + val listRandomText = listOf("MacBook Pro", "MacBook Air", "Mac Mini", "Mac Pro", "iMac") + val randomText = listRandomText.random() + val randomPrice: Double = String.format("%.2f", Random.nextDouble(0.0, 500.0)).toDouble() + val randomizeTransactionIntent = Intent(requireContext(), RandomizeTransaction::class.java) + randomizeTransactionIntent.putExtra("transactionName", randomText) + randomizeTransactionIntent.putExtra("price", randomPrice) + randomizeTransactionIntent.setAction("com.example.bondoman.services.RandomizeTransaction") + requireContext().sendBroadcast(randomizeTransactionIntent) } } - private fun showFormatSelectionDialog(listTransaction: List<SqlTransaction>) { - val formatOptions = arrayOf("XLS", "XLSX") - - val builder = AlertDialog.Builder(requireContext()) - builder.setTitle("Select File Format") - .setItems(formatOptions) { dialog, which -> - val fileFormat = if (which == 0) "xls" else "xlsx" - savedFilePath = Xls.saveXls(requireContext(), listTransaction, fileFormat) - showMassage("File has been saved successfully") - dialog.dismiss() - } - .setNegativeButton("Cancel") { dialog, _ -> - dialog.dismiss() - } - val dialog = builder.create() - dialog.show() + override fun onDestroy() { + super.onDestroy() + database.close() } - private fun showMassage(message: String) { - val alertDialogBuilder = AlertDialog.Builder(requireContext()) - alertDialogBuilder - .setMessage(message) - .setPositiveButton("OK") { dialog, _ -> - // Do something when OK button is clicked - dialog.dismiss() + private fun sendEmail() { + val items = arrayOf("xlsx", "xls") + var path: String = "" + AlertDialog.Builder(requireContext()) + .setTitle("Choose file format") + .setItems(items) { dialog, which -> + path = saveToXls(which) + val sharedPreferences = + requireActivity().getSharedPreferences( + "sharedPrefs", + Context.MODE_PRIVATE + ) + val email = sharedPreferences.getString("EMAIL", "") ?: "" + val currentDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + val intent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) + putExtra(Intent.EXTRA_SUBJECT, "Daftar Transaksi") + putExtra(Intent.EXTRA_TEXT, "Daftar Transaksi $currentDate") + type = "message/rfc822" // Email MIME type + + val file = File(path) + if (file.exists()) { + val uri: Uri = FileProvider.getUriForFile( + requireContext(), + BuildConfig.APPLICATION_ID + ".provider", + file + ) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + putExtra(Intent.EXTRA_STREAM, uri) + } + } + try { + startActivity(Intent.createChooser(intent, "Send Email")) + } catch (e: ActivityNotFoundException) { + Toast.makeText(activity, "No email app found", Toast.LENGTH_SHORT).show() + } } - .create() .show() - } - private fun sendEmail() { - // Create an intent to send an email - val intent = Intent(Intent.ACTION_SEND) - intent.type = "message/rfc822" // Email MIME type - - // Fill in the email details (optional) - intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("bintanghijriawan433@gmail.com")) - intent.putExtra(Intent.EXTRA_SUBJECT, "Subject") - intent.putExtra(Intent.EXTRA_TEXT, "Body of the email") - - try { - // Start the email activity - startActivity(Intent.createChooser(intent, "Send Email")) - } catch (e: ActivityNotFoundException) { - // Handle errors if no email client is installed - Toast.makeText(activity, "No email app found", Toast.LENGTH_SHORT).show() - } } private fun logout() { @@ -133,4 +150,54 @@ class SettingFragment: Fragment() { editor.clear() editor.apply() } + + private fun saveToXls(format: Int = -1): String { + val items = arrayOf("xlsx", "xls") + if (format==-1) { + AlertDialog.Builder(requireContext()) + .setTitle("Choose file format") + .setItems(items) { dialog, which -> + val transactions = database.transactionDao().getAll() + val internalStorageDir = requireContext().getFilesDir() + savedFilePath = Xls.saveXls( + requireContext(), + transactions, + items[which], + internalStorageDir + ) + if (savedFilePath != null) { + Toast.makeText( + requireContext(), + "File saved to $savedFilePath", + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText(requireContext(), "Failed to save file", Toast.LENGTH_SHORT) + .show() + } + } + .show() + } else { + val transactions = database.transactionDao().getAll() + val internalStorageDir = requireContext().getFilesDir() + savedFilePath = Xls.saveXls( + requireContext(), + transactions, + items[format], + internalStorageDir + ) + if (savedFilePath != null) { + Toast.makeText( + requireContext(), + "File saved to $savedFilePath", + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText(requireContext(), "Failed to save file", Toast.LENGTH_SHORT) + .show() + } + } + return savedFilePath?.toString() ?: "" + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/TransactionFragment.kt b/app/src/main/java/com/example/bondoman/TransactionFragment.kt index d7b1fbf814558e5296e75b69e07cfe1343bdad83..ed1d30c49b779a5a028d3b1aad17acd205fb414f 100644 --- a/app/src/main/java/com/example/bondoman/TransactionFragment.kt +++ b/app/src/main/java/com/example/bondoman/TransactionFragment.kt @@ -1,158 +1,131 @@ package com.example.bondoman -import android.content.ContentValues +import android.content.Intent +import android.graphics.Color import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.RelativeLayout import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.example.bondoman.retrofit.data.TransactionDB import com.example.bondoman.databinding.FragmentTransaksiBinding -import com.example.bondoman.retrofit.data.Transaction -import com.example.bondoman.helper.Xls -import com.example.bondoman.models.SqlTransaction -import com.example.bondoman.sql.Kontraktor -import com.example.bondoman.sql.TransactionSQL +import com.example.bondoman.retrofit.data.entity.TransactionEntity +import com.example.bondoman.retrofit.adapter.TransactionAdapter +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode -class TransactionFragment : Fragment() { +var pendingTransaction: MutableList<Pair<String, Double>> = mutableListOf() - private lateinit var binding : FragmentTransaksiBinding - private lateinit var transactionData : RecyclerView - private val listTransaction= ArrayList<Transaction>() +class TransactionFragment : Fragment() { + private lateinit var binding: FragmentTransaksiBinding + private lateinit var transactionData: RecyclerView + private val listTransaction = ArrayList<TransactionEntity>() private lateinit var searchBar: TextView - private lateinit var dbTransaction: TransactionSQL + lateinit var adapter: TransactionAdapter + + private lateinit var database: TransactionDB + private lateinit var db: DBViewModel + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { + database = TransactionDB.getInstance(requireContext()) binding = FragmentTransaksiBinding.inflate(inflater, container, false) + val navbar = requireActivity().findViewById<LinearLayout>(R.id.navbar_main) + val toolbar = requireActivity().findViewById<RelativeLayout>(R.id.toolbar) + navbar.setBackgroundResource(R.drawable.navbar_background) + toolbar.setBackgroundColor( + ContextCompat.getColor( + requireContext(), + R.color.black + ) + ) + val textView = toolbar.findViewById<TextView>(R.id.toolbar_text) + val transactionButton = requireActivity().findViewById<ImageButton>(R.id.transaction_button) + val graphButton = requireActivity().findViewById<ImageButton>(R.id.graph_button) + val settingButton = requireActivity().findViewById<ImageButton>(R.id.setting_button) + textView.text = "Transaksi" + textView.setTextColor(Color.WHITE) + toolbar.findViewById<ImageButton>(R.id.toolbar_back_button) + .setImageResource(R.drawable.ic_arrow_left_white) + transactionButton.isSelected = true + graphButton.isSelected = false + settingButton.isSelected = false + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - dbTransaction = TransactionSQL(requireContext()) - dbTransaction.open() - -// // Delete all data -// dbTransaction.deleteAll() -// -// // Dummy data insertion -// insertDummyTransactions() - - transactionData = binding.itemTransaction - transactionData.setHasFixedSize(true) - transactionData.layoutManager = LinearLayoutManager(requireContext()) - loadDatas(view) -// listTransaction.add(Transaction("Semen Tiga Roda","22 Maret 2024", "Pengeluaran", 200000, "Jakarta")) -// listTransaction.add(Transaction("Semen Tiga Roda","22 Maret 2024", "Pengeluaran", 300000, "Jakarta")) -// listTransaction.add(Transaction("Semen Tiga Roda","22 Maret 2024", "Pengeluaran", 300000, "Jakarta")) -// listTransaction.add(Transaction("Mapel Sirup","22 Maret 2024", "Pengeluaran", 100000, "Jakarta")) -// listTransaction.add(Transaction("Jeruks","22 Maret 2024", "Pengeluaran", 200000, "Jakarta")) -// listTransaction.add(Transaction("Apel","22 Maret 2024", "Pengeluaran", 300000, "Jakarta")) + adapter = TransactionAdapter(listTransaction) + db = ViewModelProvider(requireActivity())[DBViewModel::class.java] - val filteredList: ArrayList<Transaction> = ArrayList(listTransaction) - var transactionAdapter = TransactionAdapter(filteredList) - transactionData.adapter = transactionAdapter - - searchBar = binding.searchBar - - searchBar.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - // Not used + Log.d("PENDING LIST NOT EMPTY?", pendingTransaction.isNotEmpty().toString()) + if (pendingTransaction.isNotEmpty()) { + if (!database.isOpen){ + database.openHelper.writableDatabase } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - // Update the filteredList based on the search text - val searchText = s.toString().trim() - filteredList.clear() - - if (searchText.isEmpty()) { - // If search text is empty, show all items - filteredList.addAll(listTransaction) - } else { - // Filter the list based on the search text - filteredList.addAll(listTransaction.filter { transaction -> - transaction.name != null && transaction.name.contains(searchText, ignoreCase = true) - }) - } - - // Notify the adapter about the updated list - transactionAdapter = TransactionAdapter(filteredList) - transactionData.adapter = transactionAdapter - } - - override fun afterTextChanged(s: Editable?) { - // Not used + pendingTransaction.forEach { pair -> + Log.d("ADDED TO DB", pair.first) + db.addTransaksi(pair.first, "Pengeluaran", pair.second, "Unknown") } - }) - - - } - - private fun insertDummyTransactions() { - val dummyTransaction1 = ContentValues().apply { - put(Kontraktor.TransactionTable.NAME, "Transaction 1") - put(Kontraktor.TransactionTable.CATEGORY, "Category 1") - put(Kontraktor.TransactionTable.DATE, "2024-04-01") - put(Kontraktor.TransactionTable.PRICE, 100) - put(Kontraktor.TransactionTable.LOCATION, "Location 1") - } - - val dummyTransaction2 = ContentValues().apply { - put(Kontraktor.TransactionTable.NAME, "Transaction 2") - put(Kontraktor.TransactionTable.CATEGORY, "Category 2") - put(Kontraktor.TransactionTable.DATE, "2024-04-02") - put(Kontraktor.TransactionTable.PRICE, 200) - put(Kontraktor.TransactionTable.LOCATION, "Location 2") - } - - val dummyTransaction3 = ContentValues().apply { - put(Kontraktor.TransactionTable.NAME, "Transaction 3") - put(Kontraktor.TransactionTable.CATEGORY, "Category 3") - put(Kontraktor.TransactionTable.DATE, "2024-04-03") - put(Kontraktor.TransactionTable.PRICE, 300) - put(Kontraktor.TransactionTable.LOCATION, "Location 3") + pendingTransaction.clear() } - - val dummyTransaction4 = ContentValues().apply { - put(Kontraktor.TransactionTable.NAME, "Transaction 4") - put(Kontraktor.TransactionTable.CATEGORY, "Category 4") - put(Kontraktor.TransactionTable.DATE, "2024-04-04") - put(Kontraktor.TransactionTable.PRICE, 400) - put(Kontraktor.TransactionTable.LOCATION, "Location 4") + transactionData = binding.itemTransaction + transactionData.adapter = adapter + transactionData.layoutManager = + LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) + transactionData.addItemDecoration( + DividerItemDecoration( + requireContext(), + RecyclerView.VERTICAL + ) + ) + + binding.addButton.setOnClickListener { + val intent = Intent(requireContext(), ContainerActivity::class.java) + startActivity(intent) } - val dummyTransaction5 = ContentValues().apply { - put(Kontraktor.TransactionTable.NAME, "Transaction 5") - put(Kontraktor.TransactionTable.CATEGORY, "Category 5") - put(Kontraktor.TransactionTable.DATE, "2024-04-05") - put(Kontraktor.TransactionTable.PRICE, 500) - put(Kontraktor.TransactionTable.LOCATION, "Location 5") + binding.addButton.setOnClickListener { + val intent = Intent(requireContext(), ContainerActivity::class.java) + startActivity(intent) } + } - // Insert dummy transactions into the database - dbTransaction.insert(dummyTransaction1) - dbTransaction.insert(dummyTransaction2) - dbTransaction.insert(dummyTransaction3) - dbTransaction.insert(dummyTransaction4) - dbTransaction.insert(dummyTransaction5) + @Subscribe(threadMode = ThreadMode.MAIN) + fun onRandomTransactionReceived(event: com.example.bondoman.services.RandomTransactionEvent) { + val transactionName = event.transactionName + val price = event.price + Log.d("TRANSACTION", transactionName+ "ADDED TO PENDING TRANSACTION") + pendingTransaction.add(Pair(transactionName, price)) + } + override fun onDestroyView() { + super.onDestroyView() } - fun loadDatas(view: View) { + override fun onResume() { + super.onResume() + loadDatas() + } - insertDummyTransactions() - val datas = dbTransaction.findAll() + private fun loadDatas() { + listTransaction.clear() - datas.forEach { - item -> - listTransaction.add(Transaction(item.id,item.name,item.date,item.category, item.price,item.location)) - } + listTransaction.addAll(database.transactionDao().getAll()) + adapter.notifyDataSetChanged() } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/api/ApiInterface.kt b/app/src/main/java/com/example/bondoman/appInterface/api/ApiInterface.kt similarity index 77% rename from app/src/main/java/com/example/bondoman/api/ApiInterface.kt rename to app/src/main/java/com/example/bondoman/appInterface/api/ApiInterface.kt index 8755e984d0569c61196e68b97aca6eb3a15f59ab..62b7a1cc47ec6a75c6a5562b3a600f72d1393e41 100644 --- a/app/src/main/java/com/example/bondoman/api/ApiInterface.kt +++ b/app/src/main/java/com/example/bondoman/appInterface/api/ApiInterface.kt @@ -1,9 +1,8 @@ -package com.example.bondoman.api +package com.example.bondoman.appInterface.api import com.example.bondoman.models.LoginRequest import com.example.bondoman.models.LoginResponse import com.example.bondoman.models.JWTResponse -import com.example.bondoman.models.ScanRequest import com.example.bondoman.models.ScanResponse import okhttp3.MultipartBody import retrofit2.Response @@ -12,7 +11,6 @@ import retrofit2.http.Body import retrofit2.http.Header import retrofit2.http.Multipart import retrofit2.http.Part -import java.io.File interface ApiInterface { @POST("api/auth/login") @@ -23,8 +21,8 @@ interface ApiInterface { @Multipart @POST("api/bill/upload") - suspend fun uploadFile( - @Header("Token") token: String, + suspend fun uploadBill( + @Header("Authorization") token: String, @Part file: MultipartBody.Part - ): Response<List<ScanResponse>> + ): Response<ScanResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/helper/Xls.kt b/app/src/main/java/com/example/bondoman/helper/Xls.kt index b72942e2e2c5652b1db0e480675e8fb29f94e288..1274906bdb9a9450cb2a051c7397bb1311a3d27b 100644 --- a/app/src/main/java/com/example/bondoman/helper/Xls.kt +++ b/app/src/main/java/com/example/bondoman/helper/Xls.kt @@ -1,16 +1,22 @@ package com.example.bondoman.helper import android.content.Context -import com.example.bondoman.models.SqlTransaction +import com.example.bondoman.retrofit.data.entity.TransactionEntity import org.apache.poi.hssf.usermodel.HSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook import java.io.File import java.io.FileOutputStream +import java.time.format.DateTimeFormatter class Xls { companion object { // Dummy Transaction class for testing - fun saveXls(context: Context, transactionList: List<SqlTransaction>, fileFormat: String): String { + fun saveXls( + context: Context, + transactionList: List<TransactionEntity>, + fileFormat: String, + directory: File + ): String { // Create a new XSSFWorkbook or HSSFWorkbook based on the file format val workbook = if (fileFormat == "xlsx") XSSFWorkbook() else HSSFWorkbook() @@ -25,19 +31,24 @@ class Xls { cell.setCellValue(header) } + // Populate data rows // Populate data rows for ((index, transaction) in transactionList.withIndex()) { val row = sheet.createRow(index + 1) row.createCell(0).setCellValue(transaction.name ?: "") // Handle null name - row.createCell(1).setCellValue(transaction.date ?: "") // Handle null date - row.createCell(2).setCellValue(transaction.category ?: "") // Handle null category + + // Format the date and set it to the cell + val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd") + row.createCell(1).setCellValue(transaction.date?.format(dateFormat) ?: "") // Handle null date + + row.createCell(2).setCellValue(transaction.category.toString()) // Handle null category row.createCell(3).setCellValue(transaction.price?.toDouble() ?: 0.0) // Handle null price row.createCell(4).setCellValue(transaction.location ?: "") // Handle null location } // Write the workbook to a file - val fileName = "transaction_list.$fileFormat" - val file = File(context.filesDir, fileName) + val uniqueFileName = "transaction_list_${System.currentTimeMillis()}.$fileFormat" + val file = File(directory, uniqueFileName) FileOutputStream(file).use { workbook.write(it) } @@ -45,5 +56,4 @@ class Xls { return file.absolutePath } } - } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/models/GraphData.kt b/app/src/main/java/com/example/bondoman/models/GraphData.kt new file mode 100644 index 0000000000000000000000000000000000000000..c3d7fd06fc8482acd1c8fe305c93bf00c6fe17a1 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/models/GraphData.kt @@ -0,0 +1,9 @@ +package com.example.bondoman.models + +import androidx.room.ColumnInfo +import com.example.bondoman.retrofit.data.entity.Category + +data class GraphData( + @ColumnInfo(name = "category") val category: String?, + @ColumnInfo(name = "amount") val amount: Double? +) diff --git a/app/src/main/java/com/example/bondoman/models/ScanRequest.kt b/app/src/main/java/com/example/bondoman/models/ScanRequest.kt deleted file mode 100644 index 33d84e16bd41a296dcb795b3177a0790d81e8252..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/models/ScanRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.bondoman.models - -import java.io.File - -data class ScanRequest ( - val token: String, - val file: File, - ) \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/models/ScanResponse.kt b/app/src/main/java/com/example/bondoman/models/ScanResponse.kt index d2a02e8edd6a02a8d0d64041a66c3b0eda81f16e..6e717c07154f48f89c0d6affe98cd253094c634b 100644 --- a/app/src/main/java/com/example/bondoman/models/ScanResponse.kt +++ b/app/src/main/java/com/example/bondoman/models/ScanResponse.kt @@ -1,7 +1,21 @@ package com.example.bondoman.models -data class ScanResponse ( +data class ScanResponse( + val items: Items +) { + override fun toString() = "Response(items=$items)" +} + +data class Items( + val items: List<Item> +) { + override fun toString() = "Items(items=$items)" +} + +data class Item( val name: String, - val quantity: Int, - val price: Int, -) \ No newline at end of file + val qty: Int, + val price: Double +) { + override fun toString() = "Item(name='$name', qty=$qty, price=$price)" +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/models/SqlTransaction.kt b/app/src/main/java/com/example/bondoman/models/SqlTransaction.kt index 518665319b22648c1cff28005f3bbd2edc207fe4..173de943075c0779eeee6ed09f02baa15c2ecf48 100644 --- a/app/src/main/java/com/example/bondoman/models/SqlTransaction.kt +++ b/app/src/main/java/com/example/bondoman/models/SqlTransaction.kt @@ -3,14 +3,14 @@ package com.example.bondoman.models import android.os.Parcel import android.os.Parcelable -data class SqlTransaction(var id: Int=0, var name: String? = null, var category: String? =null, var date: String? =null, var price: Int? = null, var location : String? =null) : +data class SqlTransaction(var id: Int=0, var name: String? = null, var category: String? =null, var date: String? =null, var price: Double? = null, var location : String? =null) : Parcelable { constructor(parcel: Parcel) : this( parcel.readInt(), parcel.readString(), parcel.readString(), parcel.readString(), - parcel.readValue(Int::class.java.classLoader) as? Int, + parcel.readValue(Int::class.java.classLoader) as? Double, parcel.readString() ) diff --git a/app/src/main/java/com/example/bondoman/retrofit/adapter/TransactionAdapter.kt b/app/src/main/java/com/example/bondoman/retrofit/adapter/TransactionAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..ea8ef8acd53b89f062c6eff24ec4544d4bfc69c3 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/retrofit/adapter/TransactionAdapter.kt @@ -0,0 +1,85 @@ +package com.example.bondoman.retrofit.adapter + +import android.content.Intent +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import com.example.bondoman.ContainerActivity +import com.example.bondoman.R +import com.example.bondoman.retrofit.data.entity.TransactionEntity + + +class TransactionAdapter(private val list: ArrayList<TransactionEntity>): RecyclerView.Adapter<TransactionAdapter.ViewHolder>(){ + private lateinit var transactionDialog: Dialog + + fun setDialog(dialog: Dialog) { + this.transactionDialog = dialog + } + + interface Dialog{ + fun onClick(position: Int) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val currentData = list[position] + holder.name.text = currentData.name + holder.kategori.text = currentData.category.toString() + val tmp = "IDR " + currentData.price.toString() + holder.nominal.text = tmp + holder.lokasi.text = currentData.location + holder.tgl.text = currentData.date + + // Set click listener for itemLocation TextView + holder.lokasi.setOnClickListener { + val context = holder.itemView.context + // Check if the location is not "Unknown" and not empty before opening Google Maps + if (currentData.location != null && currentData.location != "Unknown" && currentData.location!!.isNotBlank()) { + // Open Google Maps app + val gmmIntentUri = Uri.parse("geo:0,0?q=${Uri.encode(currentData.location)}") + val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) + mapIntent.setPackage("com.google.android.apps.maps") + if (mapIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(mapIntent) + } else { + // Open Google Maps website in a web browser + val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com/maps/search/?api=1&query=${Uri.encode(currentData.location)}")) + context.startActivity(webIntent) + } + } + } + + holder.itemView.setOnClickListener { + // Display a toast message when the item is clicked + val toastMessage = "Clicked item: ${currentData.name} with id ${currentData.id}" + Toast.makeText(holder.itemView.context, toastMessage, Toast.LENGTH_SHORT).show() + + val intent = Intent(holder.itemView.context, ContainerActivity::class.java) + + // Optionally, pass data to the AddTransactionActivity + intent.putExtra("id", currentData.id) + + // Start the activity + holder.itemView.context.startActivity(intent) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view: View = LayoutInflater.from(parent.context).inflate(R.layout.row_data, parent, false) + return ViewHolder(view) + } + + override fun getItemCount(): Int { + return list.count() + } + class ViewHolder(item: View) : RecyclerView.ViewHolder(item) { + val name: TextView = item.findViewById(R.id.NamaTransaksi) + val kategori:TextView= item.findViewById(R.id.KategoriTransaksi) + val tgl: TextView= item.findViewById(R.id.TanggalTransaksi) + val nominal : TextView= item.findViewById(R.id.NominalTransaksi) + val lokasi : TextView = item.findViewById(R.id.LokasiTransaksi) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/retrofit/data/Transaction.kt b/app/src/main/java/com/example/bondoman/retrofit/data/Transaction.kt index 2e1c5d14203139055b9d22fb59ce708b8dc12fc0..9938b83cfdf0180aff48150d6b1cd1bec177bd48 100644 --- a/app/src/main/java/com/example/bondoman/retrofit/data/Transaction.kt +++ b/app/src/main/java/com/example/bondoman/retrofit/data/Transaction.kt @@ -3,13 +3,13 @@ package com.example.bondoman.retrofit.data import android.os.Parcel import android.os.Parcelable -data class Transaction(val id: Int?,val name: String?, val date: String?, val category: String?, val price: Int?, val location: String?) : Parcelable { +data class Transaction(val id: Int?,val name: String?, val date: String?, val category: String?, val price: Double?, val location: String?) : Parcelable { constructor(parcel: Parcel) : this( parcel.readValue(Int::class.java.classLoader) as? Int, parcel.readString(), parcel.readString(), parcel.readString(), - parcel.readValue(Int::class.java.classLoader) as? Int, + parcel.readValue(Int::class.java.classLoader) as? Double, parcel.readString() ) diff --git a/app/src/main/java/com/example/bondoman/retrofit/data/TransactionDB.kt b/app/src/main/java/com/example/bondoman/retrofit/data/TransactionDB.kt new file mode 100644 index 0000000000000000000000000000000000000000..30d45e837d1c8111b26cf5fd7dffb69ca7be29e7 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/retrofit/data/TransactionDB.kt @@ -0,0 +1,28 @@ +package com.example.bondoman.retrofit.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import com.example.bondoman.retrofit.data.dao.TransactionDao +import com.example.bondoman.retrofit.data.entity.TransactionEntity + +@Database(entities = [TransactionEntity::class], version = 3) +abstract class TransactionDB : RoomDatabase() { + abstract fun transactionDao(): TransactionDao + + companion object{ + private var instance : TransactionDB? = null + + fun getInstance(context: Context) : TransactionDB{ + if (instance==null){ + instance = Room.databaseBuilder(context, TransactionDB::class.java, "db") + .allowMainThreadQueries() + .fallbackToDestructiveMigration() + .build() + } + + return instance!! + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/retrofit/data/dao/TransactionDao.kt b/app/src/main/java/com/example/bondoman/retrofit/data/dao/TransactionDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..5401bb4d62e046ef436ff8eb42e49b4955f322df --- /dev/null +++ b/app/src/main/java/com/example/bondoman/retrofit/data/dao/TransactionDao.kt @@ -0,0 +1,35 @@ +package com.example.bondoman.retrofit.data.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.example.bondoman.models.GraphData +import com.example.bondoman.retrofit.data.entity.TransactionEntity + +@Dao +interface TransactionDao { + @Query("SELECT * FROM transactionentity") + fun getAll(): List<TransactionEntity> + + @Query("SELECT * FROM transactionentity WHERE id IN (:userIds)") + fun loadAllByIds(userIds: IntArray): List<TransactionEntity> + + @Query("SELECT * FROM transactionentity WHERE nama_transaksi LIKE :name AND " + + "nominal_transaksi LIKE :nominal LIMIT 1") + fun findByName(name: String, nominal: String): TransactionEntity + + @Insert + fun insertAll(vararg transactions: TransactionEntity) + + @Delete + fun delete(transaction: TransactionEntity) + + @Update + fun update(transaction: TransactionEntity) + @Query("SELECT * FROM transactionentity WHERE id = :id") + fun getId(id: Int): TransactionEntity + @Query("SELECT kategori_transaksi as category, SUM(nominal_transaksi) as amount FROM transactionentity GROUP BY kategori_transaksi") + fun sumPriceByCategory(): List<GraphData> +} diff --git a/app/src/main/java/com/example/bondoman/retrofit/data/entity/TransactionEntitiy.kt b/app/src/main/java/com/example/bondoman/retrofit/data/entity/TransactionEntitiy.kt new file mode 100644 index 0000000000000000000000000000000000000000..8661e9ab757cf45d4756020b7475fa1f5f185e32 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/retrofit/data/entity/TransactionEntitiy.kt @@ -0,0 +1,20 @@ +package com.example.bondoman.retrofit.data.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +enum class Category { + Pemasukan, + Pengeluaran +} + +@Entity +data class TransactionEntity( + @PrimaryKey(autoGenerate = true) var id: Int? = null, + @ColumnInfo(name = "nama_transaksi") var name: String? = null, + @ColumnInfo(name = "kategori_transaksi") var category: Category? = null, + @ColumnInfo(name = "tanggal_transaksi") var date: String? = null, + @ColumnInfo(name = "nominal_transaksi") var price: Double? = null, + @ColumnInfo(name = "lokasi_transaksi") var location: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ConnectivityObserver.kt b/app/src/main/java/com/example/bondoman/services/ConnectivityObserver.kt similarity index 82% rename from app/src/main/java/com/example/bondoman/ConnectivityObserver.kt rename to app/src/main/java/com/example/bondoman/services/ConnectivityObserver.kt index 52a28bc54cc1cfef65d42effdf100a34c95f8a87..24af286cca655ee8ab6f8e70e78f5611e9c70a18 100644 --- a/app/src/main/java/com/example/bondoman/ConnectivityObserver.kt +++ b/app/src/main/java/com/example/bondoman/services/ConnectivityObserver.kt @@ -1,4 +1,4 @@ -package com.example.bondoman +package com.example.bondoman.services import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/example/bondoman/NetworkSensing.kt b/app/src/main/java/com/example/bondoman/services/NetworkSensing.kt similarity index 91% rename from app/src/main/java/com/example/bondoman/NetworkSensing.kt rename to app/src/main/java/com/example/bondoman/services/NetworkSensing.kt index 34b850bd39a2cc192c3e8d3100eb1fb22f149316..466223a2da015006752a7793791bf4f48ae3fdf2 100644 --- a/app/src/main/java/com/example/bondoman/NetworkSensing.kt +++ b/app/src/main/java/com/example/bondoman/services/NetworkSensing.kt @@ -1,9 +1,9 @@ -package com.example.bondoman +package com.example.bondoman.services import android.content.Context import android.net.ConnectivityManager import kotlinx.coroutines.flow.Flow -import com.example.bondoman.ConnectivityObserver.NetworkState +import com.example.bondoman.services.ConnectivityObserver.NetworkState import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/app/src/main/java/com/example/bondoman/services/RandomTransactionEvent.kt b/app/src/main/java/com/example/bondoman/services/RandomTransactionEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..5056bcf1f2d9eea42d44d69a8ed6e2b46f0b20cb --- /dev/null +++ b/app/src/main/java/com/example/bondoman/services/RandomTransactionEvent.kt @@ -0,0 +1,3 @@ +package com.example.bondoman.services + +class RandomTransactionEvent (val transactionName: String, val price: Double) \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/services/RandomizeTransaction.kt b/app/src/main/java/com/example/bondoman/services/RandomizeTransaction.kt index 9f0e0539e4070e5664382cd71802012630ddd8a6..aebe2d6766ea1359109b63fc1a324e8276ee2fc5 100644 --- a/app/src/main/java/com/example/bondoman/services/RandomizeTransaction.kt +++ b/app/src/main/java/com/example/bondoman/services/RandomizeTransaction.kt @@ -3,25 +3,16 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent - import android.os.Handler - import android.os.Looper import android.util.Log - import android.widget.EditText - import androidx.appcompat.app.AppCompatActivity - import com.example.bondoman.LogoutFragment - import com.example.bondoman.R + import org.greenrobot.eventbus.EventBus class RandomizeTransaction : BroadcastReceiver() { - private var text: String = "" + private var transactionName: String = "" + private var price: Double = 0.0 override fun onReceive(context: Context?, intent: Intent?) { - text = intent?.getStringExtra("transactionName").toString() - - Log.d("BroadcastRandomizeTransaction", "Random text: $text") - -// val activity = context as? AppCompatActivity -// activity?.runOnUiThread { -// val fragment = activity.supportFragmentManager.findFragmentById(R.id.fragment_container) as? LogoutFragment -// fragment?.updateEditText(randomText) -// } + transactionName = intent?.getStringExtra("transactionName") ?: "" + price = intent?.getDoubleExtra("price", 0.0 ) ?: 0.0 + Log.d("BroadcastRandomizeTransaction", "Random text: $transactionName $price") + EventBus.getDefault().post(RandomTransactionEvent(transactionName, price)) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/sql/Helper.kt b/app/src/main/java/com/example/bondoman/sql/Helper.kt deleted file mode 100644 index 12130b5e00a23c80d7c4e662cfb62eb13792d639..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/sql/Helper.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.bondoman.sql - -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper - -class Helper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { - companion object { - private const val DATABASE_NAME = "bondoman" - private const val DATABASE_VERSION = 1 - - private const val SQL_CREATE = "CREATE TABLE ${Kontraktor.TransactionTable.TABLE_NAME} " + - "(${Kontraktor.TransactionTable._ID} INTEGER PRIMARY KEY AUTOINCREMENT, " + - "${Kontraktor.TransactionTable.NAME} TEXT NOT NULL, " + - "${Kontraktor.TransactionTable.CATEGORY} TEXT NOT NULL, " + - "${Kontraktor.TransactionTable.DATE} TEXT NOT NULL, " + - "${Kontraktor.TransactionTable.PRICE} INTEGER NOT NULL, " + - "${Kontraktor.TransactionTable.LOCATION} TEXT NOT NULL)" - } - - override fun onCreate(db: SQLiteDatabase) { - db.execSQL(SQL_CREATE) - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - db.execSQL("DROP TABLE IF EXISTS ${Kontraktor.TransactionTable.TABLE_NAME}") - onCreate(db) - } -} diff --git a/app/src/main/java/com/example/bondoman/sql/Kontraktor.kt b/app/src/main/java/com/example/bondoman/sql/Kontraktor.kt deleted file mode 100644 index 58f4ec7c5fdf179c26f6f5c95abde9a4764a0603..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/sql/Kontraktor.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.bondoman.sql - -import android.provider.BaseColumns - -internal class Kontraktor { - internal class TransactionTable : BaseColumns { - companion object { - const val TABLE_NAME = "transactions" - const val _ID = "_id" - const val NAME = "name" - const val CATEGORY = "category" - const val DATE = "date" - const val PRICE = "price" - const val LOCATION = "location" - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/sql/TransactionSQL.kt b/app/src/main/java/com/example/bondoman/sql/TransactionSQL.kt deleted file mode 100644 index 4d05a63bd509bf420e92e7e13a2e5c0544caf446..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/example/bondoman/sql/TransactionSQL.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.example.bondoman.sql - -import android.content.ContentValues -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import com.example.bondoman.models.SqlTransaction -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion.CATEGORY -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion.DATE -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion.LOCATION -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion.NAME -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion.PRICE -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion.TABLE_NAME -import com.example.bondoman.sql.Kontraktor.TransactionTable.Companion._ID -import java.sql.SQLException - -class TransactionSQL(context: Context) { - private var dbHelper: Helper = Helper(context) - private lateinit var database: SQLiteDatabase - - companion object { - private const val DATABASE_TABLE = TABLE_NAME - - private val INSTANCE: TransactionSQL? = null - fun getInstance(context: Context) : TransactionSQL = INSTANCE ?: synchronized(this) { - INSTANCE ?: TransactionSQL(context) - } - } - - @Throws(SQLException::class) - fun open() { - database = dbHelper.writableDatabase - } - - fun close() { - dbHelper.close() - if (database.isOpen) { - database.close() - } - } - - fun findAll():List<SqlTransaction> { - val cursor = database.query( - DATABASE_TABLE, - null,null,null,null,null,"$_ID ASC" - ) - - val listData = mutableListOf<SqlTransaction>() - - with(cursor) { - while (moveToNext()) { - val id = getInt(getColumnIndexOrThrow(_ID)) - val name = getString(getColumnIndexOrThrow(NAME)) - val category = getString(getColumnIndexOrThrow(CATEGORY)) - val date = getString(getColumnIndexOrThrow(DATE)) - val price = getInt(getColumnIndexOrThrow(PRICE)) - val location = getString(getColumnIndexOrThrow(LOCATION)) - - listData.add(SqlTransaction(id,name,category,date,price,location)) - } - cursor.close() - } - return listData - } - - fun insert(values: ContentValues?): Long { - return database.insert(DATABASE_TABLE, null, values) - } - - fun update(id: String, values: ContentValues?): Int { - return database.update(DATABASE_TABLE, values, "$_ID = ?", arrayOf(id)) - } - - fun delete(id: String): Int { - return database.delete(DATABASE_TABLE, "$_ID = '$id'", null) - } - - fun deleteAll() { - database.delete(DATABASE_TABLE, null, null) - } - -} diff --git a/app/src/main/java/com/example/bondoman/utils/RetrofitInstance.kt b/app/src/main/java/com/example/bondoman/utils/RetrofitInstance.kt index b9bee5f00a0815157b618706909992f3cb7d8626..6f71f155f7cc36b05212e5515c07fa0bfb47de59 100644 --- a/app/src/main/java/com/example/bondoman/utils/RetrofitInstance.kt +++ b/app/src/main/java/com/example/bondoman/utils/RetrofitInstance.kt @@ -1,6 +1,6 @@ package com.example.bondoman.utils -import com.example.bondoman.api.ApiInterface +import com.example.bondoman.appInterface.api.ApiInterface import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory diff --git a/app/src/main/res/drawable/baseline_add_24.xml b/app/src/main/res/drawable/baseline_add_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..89633bb125d37a7e89833cfd4708ae8b8902822d --- /dev/null +++ b/app/src/main/res/drawable/baseline_add_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#000000" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> +</vector> diff --git a/app/src/main/res/drawable/component_7.xml b/app/src/main/res/drawable/component_7.xml new file mode 100644 index 0000000000000000000000000000000000000000..26e09f08e75530ec4bec1a5e8ebe1e94dd90cde0 --- /dev/null +++ b/app/src/main/res/drawable/component_7.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="54dp" + android:height="52dp" + android:viewportWidth="54" + android:viewportHeight="52"> + <path + android:pathData="M24,0L30,0A20,20 0,0 1,50 20L50,24A20,20 0,0 1,30 44L24,44A20,20 0,0 1,4 24L4,20A20,20 0,0 1,24 0z" + android:fillColor="#9290C3"/> + <path + android:pathData="M27,18.426V25.573" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M30.578,22H23.422" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M27,12.263C34.302,12.263 36.737,14.698 36.737,22C36.737,29.302 34.302,31.737 27,31.737C19.698,31.737 17.263,29.302 17.263,22C17.263,16.03 18.891,13.313 23.475,12.521" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> +</vector> diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 0000000000000000000000000000000000000000..3ff265b5f755197a47d3da49ba51be3833313b45 --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="30dp" + android:height="30dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M11.99,21.242C4.225,21.242 5.403,17.617 5.403,9.598" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M18.599,9.598C18.599,15.827 19.295,19.405 15.967,20.692" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M20.075,6.524H3.925" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M15.425,6.524C15.425,6.524 15.954,2.758 11.999,2.758C10.27,2.758 9.398,3.479 8.964,4.29" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> +</vector> diff --git a/app/src/main/res/drawable/ic_arrow_left_black.xml b/app/src/main/res/drawable/ic_arrow_left_black.xml new file mode 100644 index 0000000000000000000000000000000000000000..9edb1375a5117c337be687fb01b15d94edeb23ed --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left_black.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16.727,12.005H19.5" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M4.5,12.005H13.123" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> + <path + android:pathData="M10.55,5.975C10.55,5.975 4.5,9.235 4.5,11.995C4.5,14.765 10.55,18.025 10.55,18.025" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#000000" + android:strokeLineCap="round"/> +</vector> diff --git a/app/src/main/res/drawable/ic_arrow_left_white.xml b/app/src/main/res/drawable/ic_arrow_left_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..1fbf3d563514db9c9c96feccf04979ef46182d89 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left_white.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16.727,12.005H19.5" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M4.5,12.005H13.123" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="M10.55,5.975C10.55,5.975 4.5,9.235 4.5,11.995C4.5,14.765 10.55,18.025 10.55,18.025" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:fillColor="#00000000" + android:strokeColor="#ffffff" + android:strokeLineCap="round"/> +</vector> diff --git a/app/src/main/res/drawable/navbar_background_vertical.xml b/app/src/main/res/drawable/navbar_background_vertical.xml new file mode 100644 index 0000000000000000000000000000000000000000..f7870684f1164affa92c72b9b0bf63766516a313 --- /dev/null +++ b/app/src/main/res/drawable/navbar_background_vertical.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="58dp" + android:height="202dp" + android:viewportWidth="58" + android:viewportHeight="202"> + <path + android:pathData="M0,182L0,20A20,20 0,0 1,20 0L38,0A20,20 0,0 1,58 20L58,182A20,20 0,0 1,38 202L20,202A20,20 0,0 1,0 182z" + android:fillColor="#1B1A55"/> +</vector> diff --git a/app/src/main/res/drawable/navbar_border.xml b/app/src/main/res/drawable/navbar_border.xml new file mode 100644 index 0000000000000000000000000000000000000000..9c282b97861458c799007738633d90a26e77962e --- /dev/null +++ b/app/src/main/res/drawable/navbar_border.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@android:color/transparent" /> + <stroke android:width="1dp" android:color="#000000" /> + <corners android:radius="20dp"/> +</shape> \ No newline at end of file diff --git a/app/src/main/res/drawable/navbar_bordered_background.xml b/app/src/main/res/drawable/navbar_bordered_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9879e51abbdfd0d98665a57dbdb023704069a35 --- /dev/null +++ b/app/src/main/res/drawable/navbar_bordered_background.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/navbar_background"/> + <item android:drawable="@drawable/navbar_border"/> +</layer-list> \ No newline at end of file diff --git a/app/src/main/res/drawable/rainbow_off.xml b/app/src/main/res/drawable/rainbow_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5af62c8789d81b1fa33d52fc3663959c317fe25 --- /dev/null +++ b/app/src/main/res/drawable/rainbow_off.xml @@ -0,0 +1,21 @@ +<vector android:height="100dp" android:viewportHeight="24" + android:viewportWidth="24" android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <group> + <clip-path android:pathData="M0,0h24v24h-24z"/> + <path android:fillColor="#00000000" + android:pathData="M7.878,7.886C6.126,8.679 4.639,9.961 3.597,11.577C2.554,13.194 2,15.076 2,17M22,17C22,11.477 17.523,7 12,7C11.692,7 11.387,7.014 11.086,7.041L22,17Z" + android:strokeColor="#0F172A" android:strokeLineCap="round" + android:strokeLineJoin="round" android:strokeWidth="2"/> + <path android:fillColor="#00000000" + android:pathData="M11.088,11.069C9.671,11.287 8.38,12.005 7.446,13.093C6.513,14.181 6,15.567 6,17" + android:strokeColor="#0F172A" android:strokeLineCap="round" + android:strokeLineJoin="round" android:strokeWidth="2"/> + <path android:fillColor="#00000000" + android:pathData="M14,17C14,16.47 13.789,15.961 13.414,15.586C13.039,15.211 12.53,15 12,15C11.47,15 10.961,15.211 10.586,15.586C10.211,15.961 10,16.47 10,17" + android:strokeColor="#0F172A" android:strokeLineCap="round" + android:strokeLineJoin="round" android:strokeWidth="2"/> + <path android:fillColor="#00000000" + android:pathData="M3,3L21,21" android:strokeColor="#0F172A" + android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2"/> + </group> +</vector> diff --git a/app/src/main/res/layout-land/navbar_layout.xml b/app/src/main/res/layout-land/navbar_layout.xml new file mode 100644 index 0000000000000000000000000000000000000000..165f0cc1e9f6a881ff899ce7d2b0b669544f8a28 --- /dev/null +++ b/app/src/main/res/layout-land/navbar_layout.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <LinearLayout + android:layout_width="98dp" + android:layout_height="340dp" + android:background="@android:color/transparent" + android:orientation="vertical" + android:layout_marginTop="?attr/actionBarSize" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginBottom="60dp" + android:layout_marginTop="60dp" + android:layout_marginEnd="40dp" + android:orientation="vertical" + android:background="@android:color/transparent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" > + <LinearLayout + android:id="@+id/navbar_scanner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:orientation="vertical" + app:layout_constraintBottom_toTopOf="@+id/navbar_scanner" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"> + <ImageButton + android:id="@+id/scan_button" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1" + android:background="@drawable/scanner_background" + android:contentDescription="@string/scan_button_description" + android:src="@drawable/ic_scanner_white" /> + </LinearLayout> + <LinearLayout + android:id="@+id/navbar_main" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="18dp" + android:background="@drawable/navbar_background_vertical" + android:orientation="vertical" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/navbar_scanner" + app:layout_constraintBottom_toBottomOf="parent"> + <ImageButton + android:id="@+id/setting_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:background="@android:color/transparent" + android:contentDescription="@string/setting_button_description" + android:src="@drawable/ic_setting" /> + <ImageButton + android:id="@+id/graph_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:background="@android:color/transparent" + android:contentDescription="@string/graph_button_description" + android:src="@drawable/ic_grafik" /> + <ImageButton + android:id="@+id/transaction_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:background="@android:color/transparent" + android:contentDescription="@string/transaction_button_description" + android:src="@drawable/ic_transaksi" /> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_container.xml b/app/src/main/res/layout/activity_container.xml new file mode 100644 index 0000000000000000000000000000000000000000..4cddab99c4c0d0b75deb782dc1ea978f4bd20666 --- /dev/null +++ b/app/src/main/res/layout/activity_container.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/main" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ContainerActivity"> + <androidx.fragment.app.FragmentContainerView + android:id="@+id/fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index b8db39fbaff37b4a7db3d02c9130dcc0afcf3cfd..fe450f733aef6427bfe67e1b92abf1f9439a7ff3 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -17,6 +17,50 @@ app:layout_constraintEnd_toEndOf="parent" app:srcCompat="@drawable/login" /> + + <RelativeLayout + android:id="@+id/conn_lost" + android:layout_width="match_parent" + android:layout_height="0dp" + android:gravity="center" + android:padding="16dp" + app:layout_constraintTop_toBottomOf="@id/login_page" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent"> + + + <ImageView + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:src="@drawable/rainbow_off" /> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/image" + android:layout_centerHorizontal="true" + android:layout_marginTop="10dp" + android:text="Connection Lost" + android:textAppearance="?attr/textAppearanceHeadline6" /> + + <TextView + android:id="@+id/message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/title" + android:layout_centerHorizontal="true" + android:layout_marginTop="8dp" + android:text="Please check your internet connection and try again." + android:textAppearance="?attr/textAppearanceBody2" /> + + + + </RelativeLayout> + <EditText android:id="@+id/emailAddress" android:layout_width="300dp" diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9db9a1173c4415acf07045b13dffe48b190374d9..bb43b2ebeebada1688cb22b4896ef32b2d7ff0f8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,19 +6,34 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - <androidx.fragment.app.FragmentContainerView - android:id="@+id/nav_host_fragment" - android:name="androidx.navigation.fragment.NavHostFragment" + <include + layout="@layout/toolbar" + android:id="@+id/toolbar" android:layout_width="match_parent" - android:layout_height="match_parent" - app:defaultNavHost="true" - app:navGraph="@navigation/nav_graph" /> + android:layout_height="?attr/actionBarSize" + app:layout_constraintTop_toTopOf="parent"/> - <include - layout="@layout/navbar_layout" + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" /> + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintBottom_toBottomOf="parent"> + <androidx.fragment.app.FragmentContainerView + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_constraintTop_toTopOf="parent" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" /> + + <include + android:id="@+id/navigation_bar" + layout="@layout/navbar_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" /> + </androidx.constraintlayout.widget.ConstraintLayout> <!-- <FrameLayout--> <!-- android:id="@+id/fragment_container"--> <!-- android:layout_width="match_parent"--> diff --git a/app/src/main/res/layout/fragment_add_transaction.xml b/app/src/main/res/layout/fragment_add_transaction.xml index cbbbce3463e51ed579bd67ab49ddb6ffa1cd2d38..8af484896a69cf5e3682625f54c387d5a82ec72d 100644 --- a/app/src/main/res/layout/fragment_add_transaction.xml +++ b/app/src/main/res/layout/fragment_add_transaction.xml @@ -37,10 +37,11 @@ /> <EditText + android:id="@+id/add_name_field" android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginTop="8dp" - android:text="Nama Transaksi" + android:hint="Masukkan nama transaksi" android:textColor="@color/grey" android:paddingHorizontal="20dp" android:textSize="16sp" @@ -56,10 +57,11 @@ /> <EditText + android:id="@+id/add_price_field" android:layout_width="match_parent" android:layout_marginTop="8dp" android:layout_height="48dp" - android:text="Nominal" + android:hint="Masukkan nominal" android:textColor="@color/grey" android:paddingHorizontal="20dp" android:textSize="16sp" @@ -75,10 +77,11 @@ /> <EditText + android:id="@+id/add_location_field" android:layout_width="match_parent" android:layout_marginTop="8dp" android:layout_height="48dp" - android:text="Lokasi" + android:hint="Masukkan lokasi" android:textColor="@color/grey" android:paddingHorizontal="20dp" android:textSize="16sp" @@ -93,16 +96,15 @@ android:textColor="@color/dark_grey" /> - <EditText + <Spinner + android:id="@+id/add_category_field" android:layout_width="match_parent" android:layout_marginTop="8dp" android:layout_height="48dp" - android:text="Kategori" android:textColor="@color/grey" android:paddingHorizontal="20dp" android:textSize="16sp" - android:background="@drawable/cornered_add" - /> + android:background="@drawable/cornered_add" /> <LinearLayout android:layout_width="match_parent" @@ -110,6 +112,7 @@ android:layout_weight="1" > <Button + android:id="@+id/add_button" android:layout_width="match_parent" android:layout_height="56dp" android:layout_gravity="bottom" diff --git a/app/src/main/res/layout/fragment_detail_transaction.xml b/app/src/main/res/layout/fragment_detail_transaction.xml deleted file mode 100644 index d175656c33cf67c56c8203031938c0645fe35920..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/fragment_detail_transaction.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".DetailTransactionFragment"> - - <!-- TODO: Update blank fragment layout --> - <TextView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/hello_blank_fragment" /> - -</FrameLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_edit_transaction.xml b/app/src/main/res/layout/fragment_edit_transaction.xml index f4cb513d8c5dedad844e66325cb9b2f1fde8d4b1..4339a789075cd8e585e4834779b9f54e69203a09 100644 --- a/app/src/main/res/layout/fragment_edit_transaction.xml +++ b/app/src/main/res/layout/fragment_edit_transaction.xml @@ -12,16 +12,26 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/container_edit" - android:layout_marginTop="24dp" + android:layout_marginTop="75dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"> </ImageView> + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/deleteButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/delete" + app:backgroundTint="@color/broken_white" + tools:ignore="MissingConstraints" + tools:layout_editor_absoluteX="290dp" + tools:layout_editor_absoluteY="-9dp" /> + <LinearLayout android:layout_width="match_parent" android:layout_height="500dp" - android:layout_marginTop="64dp" + android:layout_marginTop="100dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -37,10 +47,11 @@ /> <EditText + android:id="@+id/nameField" android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginTop="8dp" - android:text="Nama Transaksi" + android:hint="Masukkan nama transaksi" android:textColor="@color/grey" android:paddingHorizontal="20dp" android:textSize="16sp" @@ -56,10 +67,11 @@ /> <EditText + android:id="@+id/priceField" android:layout_width="match_parent" android:layout_marginTop="8dp" android:layout_height="48dp" - android:text="Nominal" + android:hint="Masukkan nominal" android:textColor="@color/grey" android:paddingHorizontal="20dp" android:textSize="16sp" @@ -75,10 +87,11 @@ /> <EditText + android:id="@+id/locationField" android:layout_width="match_parent" android:layout_marginTop="8dp" android:layout_height="48dp" - android:text="Lokasi" + android:hint="Masukkan Lokasi" android:textColor="@color/grey" android:paddingVertical="12dp" android:paddingHorizontal="20dp" @@ -88,6 +101,7 @@ <TextView + android:id="@+id/categoryField" android:layout_width="match_parent" android:layout_marginTop="24dp" android:layout_height="48dp" @@ -100,6 +114,7 @@ /> <TextView + android:id="@+id/dateField" android:layout_width="match_parent" android:layout_marginTop="24dp" android:layout_height="48dp" @@ -112,6 +127,7 @@ /> <Button + android:id="@+id/saveButton" android:layout_width="match_parent" android:layout_height="56dp" android:layout_marginTop="24dp" diff --git a/app/src/main/res/layout/activity_scan.xml b/app/src/main/res/layout/fragment_scan.xml similarity index 71% rename from app/src/main/res/layout/activity_scan.xml rename to app/src/main/res/layout/fragment_scan.xml index 0a474cc09ce8222b269ff767069d63d8a481b566..753bb2876340608faf1c95f8ed40d6bf9e20e726 100644 --- a/app/src/main/res/layout/activity_scan.xml +++ b/app/src/main/res/layout/fragment_scan.xml @@ -1,11 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/scan_fragment" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:background="#1B1A55"> + <ImageView + android:id="@+id/image_conn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:src="@drawable/rainbow_off" /> + + <TextView + android:id="@+id/title_conn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/image_conn" + android:layout_centerHorizontal="true" + android:layout_marginTop="10dp" + android:text="Connection Lost" + android:textAppearance="?attr/textAppearanceHeadline6" /> + + <TextView + android:id="@+id/message_conn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/title_conn" + android:layout_centerHorizontal="true" + android:layout_marginTop="8dp" + android:text="Please check your internet connection and try again." + android:textAppearance="?attr/textAppearanceBody2" /> <FrameLayout android:id="@+id/FrameLayout" @@ -13,7 +39,7 @@ android:layout_height="435dp" android:layout_marginEnd="30dp" android:layout_marginStart="30dp" - android:layout_marginTop="100dp" + android:layout_marginTop="10dp" android:layout_marginBottom="200dp" android:orientation="horizontal" android:layout_centerHorizontal="true" @@ -28,7 +54,7 @@ android:layout_height="match_parent" android:scaleType="fitCenter" android:adjustViewBounds="true" - + android:contentDescription="@string/selected_image_description" android:id="@+id/imageView" android:visibility="gone"/> <View @@ -37,7 +63,6 @@ android:background="@drawable/effect_rounded_edge"/> </FrameLayout> - <ImageButton android:layout_width="80dp" android:layout_height="80dp" @@ -86,6 +111,9 @@ android:layout_marginEnd="100dp" android:visibility="invisible"/> - - -</RelativeLayout> \ No newline at end of file +</RelativeLayout> +<!--android:id="@+id/scan_conn_lost"--> +<!--android:layout_width="match_parent"--> +<!--android:layout_height="match_parent"--> +<!--android:gravity="center"--> +<!--android:padding="16dp">--> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_scanner.xml b/app/src/main/res/layout/fragment_scanner.xml deleted file mode 100644 index f9f6f43af94a315b6bbd229046e3ef86f98ac26d..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/fragment_scanner.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <SurfaceView - android:id="@+id/surface_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingStart="20dp" - android:paddingTop="20dp" - android:paddingEnd="20dp" - android:paddingBottom="20dp" /> - -</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tambah_transaksi.xml b/app/src/main/res/layout/fragment_tambah_transaksi.xml new file mode 100644 index 0000000000000000000000000000000000000000..9f974debc408cf6950a9a422ed53895397e63615 --- /dev/null +++ b/app/src/main/res/layout/fragment_tambah_transaksi.xml @@ -0,0 +1,95 @@ +<?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="wrap_content" + android:orientation="vertical" + android:padding="20dp" + android:layout_margin="20dp" + tools:context=".TambahTransaksi"> + + <com.google.android.material.textfield.TextInputLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + app:boxBackgroundMode="outline" + android:layout_marginBottom="15dp"> + <EditText + android:id="@+id/NamaTransaksi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPersonName" + android:hint="Nama Transaksi" + /> + </com.google.android.material.textfield.TextInputLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_marginBottom="15dp"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Kategori" + android:textSize="16sp" + android:layout_marginStart="15dp" + android:layout_marginBottom="10dp" + /> + <RadioGroup + android:id="@+id/KategoriTransaksi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginBottom="14dp" + android:layout_marginStart="15dp"> + <RadioButton + android:id="@+id/Pemasukan" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Pemasukan" + android:checked="true" + /> + <RadioButton + android:id="@+id/Pengeluaran" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Pengeluaran" + /> + </RadioGroup> + </LinearLayout> + <com.google.android.material.textfield.TextInputLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + app:boxBackgroundMode="outline" + android:layout_marginBottom="15dp"> + <EditText + android:id="@+id/NominalTransaksi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="numberDecimal" + android:hint="Masukkan Nominal Transaksi" + /> + </com.google.android.material.textfield.TextInputLayout> + <com.google.android.material.textfield.TextInputLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + app:boxBackgroundMode="outline" + android:layout_marginBottom="25dp"> + <EditText + android:id="@+id/LokasiTransaksi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textLongMessage" + android:hint="Lokasi Transaksi" + /> + </com.google.android.material.textfield.TextInputLayout> + <Button + android:id="@+id/SimpanTransaksi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Simpan" + android:backgroundTint="@color/purple" + android:textColor="@color/white" + android:layout_marginBottom="25dp" + /> +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_transaksi.xml b/app/src/main/res/layout/fragment_transaksi.xml index c6130c515d9a4d535e6f9fe56f97a1b19fcbbce5..c366b50fffedb12f320287cc08c089f82c176c46 100644 --- a/app/src/main/res/layout/fragment_transaksi.xml +++ b/app/src/main/res/layout/fragment_transaksi.xml @@ -59,6 +59,7 @@ </LinearLayout> <ImageView + android:id="@+id/filterButton" android:layout_width="50dp" android:layout_height="50dp" android:layout_weight="1" @@ -67,13 +68,31 @@ </LinearLayout> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/itemTransaction" + <RelativeLayout 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:layout_marginTop="100dp" - android:layout_marginHorizontal="20dp" - tools:listitem="@layout/list_transaction" /> + android:layout_height="match_parent"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/itemTransaction" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="100dp" + android:layout_marginBottom="?attr/actionBarSize" + android:layout_marginHorizontal="20dp" + > + </androidx.recyclerview.widget.RecyclerView> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/addButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@color/purple" + android:src="@drawable/baseline_add_24" + android:layout_marginHorizontal="315dp" + android:layout_marginTop="440dp"/> + </RelativeLayout> </LinearLayout> diff --git a/app/src/main/res/layout/navbar_layout.xml b/app/src/main/res/layout/navbar_layout.xml index 923eac33e7cadfaacf5ac029ea757735831492c0..17f712eaf2fc533ed7e2ee64142e59f699fb6c10 100644 --- a/app/src/main/res/layout/navbar_layout.xml +++ b/app/src/main/res/layout/navbar_layout.xml @@ -3,7 +3,6 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> - <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -12,19 +11,17 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> - <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="60dp" android:layout_marginEnd="60dp" android:layout_marginBottom="40dp" - android:background="@android:color/transparent" android:orientation="horizontal" + android:background="@android:color/transparent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> - <LinearLayout android:id="@+id/navbar_main" android:layout_width="wrap_content" @@ -35,17 +32,14 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/navbar_scanner" app:layout_constraintStart_toStartOf="parent"> - <ImageButton android:id="@+id/transaction_button" - android:clickable="true" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" android:background="@android:color/transparent" android:contentDescription="@string/transaction_button_description" android:src="@drawable/ic_transaksi" /> - <ImageButton android:id="@+id/graph_button" android:layout_width="wrap_content" @@ -54,7 +48,6 @@ android:background="@android:color/transparent" android:contentDescription="@string/graph_button_description" android:src="@drawable/ic_grafik" /> - <ImageButton android:id="@+id/setting_button" android:layout_width="wrap_content" @@ -64,7 +57,6 @@ android:contentDescription="@string/setting_button_description" android:src="@drawable/ic_setting" /> </LinearLayout> - <LinearLayout android:id="@+id/navbar_scanner" android:layout_width="wrap_content" @@ -74,7 +66,6 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/navbar_main"> - <ImageButton android:id="@+id/scan_button" android:layout_width="0dp" @@ -86,12 +77,4 @@ </LinearLayout> </LinearLayout> </LinearLayout> - - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/guideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layout_constraintGuide_begin="20dp" /> </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/popup_connection_lost.xml b/app/src/main/res/layout/popup_connection_lost.xml new file mode 100644 index 0000000000000000000000000000000000000000..1ac9708d84e79d5184b19cf27c70cdc5781a264b --- /dev/null +++ b/app/src/main/res/layout/popup_connection_lost.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/scan_conn_lost" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:padding="16dp"> + + <ImageView + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:src="@drawable/rainbow_off" /> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/image" + android:layout_centerHorizontal="true" + android:layout_marginTop="10dp" + android:text="Connection Lost" + android:textAppearance="?attr/textAppearanceHeadline6" /> + + <TextView + android:id="@+id/message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/title" + android:layout_centerHorizontal="true" + android:layout_marginTop="8dp" + android:text="Please check your internet connection and try again." + android:textAppearance="?attr/textAppearanceBody2" /> + + + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/row_data.xml b/app/src/main/res/layout/row_data.xml new file mode 100644 index 0000000000000000000000000000000000000000..1ca99191441741b0d3986a620bbf18afc14af522 --- /dev/null +++ b/app/src/main/res/layout/row_data.xml @@ -0,0 +1,64 @@ +<?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="wrap_content" + android:layout_margin="8dp" + android:orientation="horizontal"> + <LinearLayout + android:layout_width="220dp" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_margin="8dp"> + <TextView + android:id="@+id/KategoriTransaksi" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/cornered_simpan" + android:textColor="@color/light_grey" + android:padding="4dp" + android:textSize="12sp" + /> + <TextView + android:id="@+id/NamaTransaksi" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="2dp" + android:textSize="16sp" + /> + <TextView + android:id="@+id/TanggalTransaksi" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="2dp" + android:textSize="12sp" + /> + </LinearLayout> + <LinearLayout + android:layout_width="90dp" + android:layout_height="match_parent" + android:layout_marginEnd="5dp" + android:layout_marginStart="0dp" + android:orientation="vertical" + android:gravity="end"> + <TextView + android:id="@+id/NominalTransaksi" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="2dp" + android:textSize="16sp" + android:layout_weight="1"/> + <TextView + android:id="@+id/LokasiTransaksi" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="2dp" + android:textSize="12sp" + android:ellipsize="end" + android:maxLines="1" + android:layout_weight="2" + android:clickable="true" /> + + + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c9e16fa507e68dfc84fa0270128af7a6a35b3d0 --- /dev/null +++ b/app/src/main/res/layout/toolbar.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="@android:color/transparent"> + <ImageButton + android:id="@+id/toolbar_back_button" + android:layout_width="?attr/actionBarSize" + android:layout_height="match_parent" + android:src="@drawable/ic_arrow_left_white" + android:background="@android:color/transparent" + android:layout_alignParentStart="true" + android:contentDescription="@string/back_button_description"/> + <TextView + android:id="@+id/toolbar_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:textColor="@color/white" + android:layout_centerInParent="true"/> + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 98d14d3364b40fb2aa9f45340e0285afc9dbbf4e..137a3668e0fa81cb75f8cc971293e272ac7fe347 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -16,9 +16,16 @@ tools:layout="@layout/fragment_grafik" android:name="com.example.bondoman.GraphFragment"> </fragment> - <fragment android:id="@+id/setting_fragment" + <fragment + android:id="@+id/setting_fragment" android:label="@string/setting_button_description" tools:layout="@layout/fragment_setting" android:name="com.example.bondoman.SettingFragment"> </fragment> + <fragment + android:id="@+id/scan_fragment" + android:name="com.example.bondoman.ScanFragment" + android:label="@string/scan_button_description" + tools:layout="@layout/fragment_scan"> + </fragment> </navigation> \ 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 607d363b367ed7492ec9b6f417d2be177d7def9f..5de3deffd40fa7984561a376fdd94b7612db0aae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,4 +14,13 @@ <string name="image_button_description">Galeri</string> <string name="confirm_button_description">Konfirmasi</string> <string name="hello_blank_fragment">Hello blank fragment</string> + <string-array name="category_array"> + <item>Pemasukan</item> + <item>Pengeluaran</item> + </string-array> + <string name="back_button_description">Kembali</string> + <string name="selected_image_description">Gambar Terpilih</string> + <string name="scan_fragment_title">Scan Nota</string> + + </resources> \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000000000000000000000000000000000000..89e3f0e09b6c08dab4c8ba08c7f8e6b5302ab29c --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <files-path name="internal_files" path="."/> +</paths> \ No newline at end of file