From 920a81cd9508e992140128964dd2ace858ec1891 Mon Sep 17 00:00:00 2001 From: "Moch. Sofyan Firdaus" <13521083@std.stei.itb.ac.id> Date: Thu, 4 Apr 2024 02:40:09 +0700 Subject: [PATCH] feat: save excel to storage --- app/build.gradle.kts | 9 ++ .../bondoman/room/TransactionCategory.kt | 3 +- .../bondoman/ui/settings/SettingsFragment.kt | 110 +++++++++++++++++- .../onionsquad/bondoman/util/Converters.kt | 6 +- .../main/res/drawable/ic_baseline_save_24.xml | 5 + app/src/main/res/layout/fragment_settings.xml | 37 +++++- app/src/main/res/values/arrays.xml | 7 ++ app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 2 + 9 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_save_24.xml create mode 100644 app/src/main/res/values/arrays.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f50af94..adfc4c0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,6 +34,12 @@ android { buildFeatures { viewBinding = true } + + packaging { + resources { + excludes += "META-INF/*" + } + } } dependencies { @@ -67,4 +73,7 @@ dependencies { // Work implementation(libs.androidx.work.runtime) + + // Excel + implementation(libs.excelkt) } \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/room/TransactionCategory.kt b/app/src/main/java/com/onionsquad/bondoman/room/TransactionCategory.kt index a49c479..f37764b 100644 --- a/app/src/main/java/com/onionsquad/bondoman/room/TransactionCategory.kt +++ b/app/src/main/java/com/onionsquad/bondoman/room/TransactionCategory.kt @@ -2,5 +2,6 @@ package com.onionsquad.bondoman.room enum class TransactionCategory { INCOME, - OUTCOME + OUTCOME, + UNKNOWN } \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt b/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt index 37c78c8..671dffa 100644 --- a/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/onionsquad/bondoman/ui/settings/SettingsFragment.kt @@ -2,22 +2,65 @@ package com.onionsquad.bondoman.ui.settings import android.app.AlertDialog import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ArrayAdapter import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.onionsquad.bondoman.R import com.onionsquad.bondoman.auth.AutoLogoutWorker import com.onionsquad.bondoman.auth.SessionManager import com.onionsquad.bondoman.databinding.FragmentSettingsBinding +import com.onionsquad.bondoman.repository.TransactionRepository +import com.onionsquad.bondoman.room.TransactionCategory +import com.onionsquad.bondoman.room.TransactionDatabase +import com.onionsquad.bondoman.room.TransactionEntity +import io.github.evanrupert.excelkt.workbook +import org.apache.poi.ss.usermodel.FillPatternType +import org.apache.poi.ss.usermodel.IndexedColors +import java.io.OutputStream +import java.time.ZoneId +import java.time.format.DateTimeFormatter class SettingsFragment : Fragment() { private var _binding: FragmentSettingsBinding? = null private val binding get() = _binding!! + private val database by lazy { TransactionDatabase.getInstance(requireContext()) } + private val repository by lazy { TransactionRepository(database.transactionDao()) } + + + private val saveXls = + registerForActivityResult(CreateDocument("application/vnd.ms-excel")) { uri -> + if (uri != null) { + repository.listTransactions.observe(viewLifecycleOwner) { list -> + Log.d(this::class.java.simpleName, "Saving transactions to $uri") + requireContext().contentResolver.openOutputStream(uri)?.use { fos -> + createExcelFile(list, fos) + } + } + } + } + + private val saveXlsx = + registerForActivityResult( + CreateDocument("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + ) { uri -> + if (uri != null) { + repository.listTransactions.observe(viewLifecycleOwner) { list -> + Log.d(this::class.java.simpleName, "Saving transactions to $uri") + requireContext().contentResolver.openOutputStream(uri)?.use { fos -> + createExcelFile(list, fos) + } + } + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -26,7 +69,7 @@ class SettingsFragment : Fragment() { _binding = FragmentSettingsBinding.inflate(inflater, container, false) binding.apply { - logoutButton.setOnClickListener { + buttonLogout.setOnClickListener { val alertBuilder = AlertDialog.Builder(requireContext()) alertBuilder.setTitle(R.string.title_alert_logout) alertBuilder.setMessage(R.string.message_alert_logout) @@ -39,6 +82,22 @@ class SettingsFragment : Fragment() { } alertBuilder.show() } + + ArrayAdapter.createFromResource( + requireContext(), + R.array.excel_types, + android.R.layout.simple_spinner_dropdown_item + ).also { arrayAdapter -> + arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + spinnerExcelType.adapter = arrayAdapter + } + + buttonSave.setOnClickListener { + when (spinnerExcelType.selectedItem.toString()) { + "XLS" -> saveXls.launch("transactions.xls") + "XLSX" -> saveXlsx.launch("transactions.xlsx") + } + } } return binding.root @@ -56,4 +115,53 @@ class SettingsFragment : Fragment() { findNavController().popBackStack(R.id.navigation_transaction, true) requireActivity().recreate() } + + private fun createExcelFile(data: List<TransactionEntity>, outputStream: OutputStream) { + workbook { + val headers = arrayOf( + "Tanggal", + "Kategori Transaksi", + "Nominal Transaksi", + "Nama Transaksi", + "Lokasi" + ) + val sheet = sheet { + val headingStyle = createCellStyle { + val font = createFont { bold = true } + setFont(font) + fillPattern = FillPatternType.SOLID_FOREGROUND + fillForegroundColor = IndexedColors.AQUA.index + } + row(headingStyle) { + headers.forEach { cell(it) } + } + for (transaction in data) { + row { + val date = transaction.date + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime() + .format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) + cell(date) + cell(transaction.category.text()) + cell(transaction.amount) + cell(transaction.title) + cell(transaction.location) + } + } + }.xssfSheet + for (i in 0..headers.size) { + sheet.setColumnWidth(i, 30 * 256) + } + }.xssfWorkbook.write(outputStream) + } + + private fun TransactionCategory.text(): String { + return when (this) { + TransactionCategory.INCOME -> "Pemasukan" + TransactionCategory.OUTCOME -> "Pengeluaran" + TransactionCategory.UNKNOWN -> "" + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/onionsquad/bondoman/util/Converters.kt b/app/src/main/java/com/onionsquad/bondoman/util/Converters.kt index eb9f90a..ce6bedd 100644 --- a/app/src/main/java/com/onionsquad/bondoman/util/Converters.kt +++ b/app/src/main/java/com/onionsquad/bondoman/util/Converters.kt @@ -25,6 +25,10 @@ object Converters { @TypeConverter fun toTransactionCategory(categoryString: String): TransactionCategory { - return TransactionCategory.valueOf(categoryString) + return try { + TransactionCategory.valueOf(categoryString) + } catch (e: IllegalArgumentException) { + TransactionCategory.UNKNOWN + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_save_24.xml b/app/src/main/res/drawable/ic_baseline_save_24.xml new file mode 100644 index 0000000..cfd40f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_save_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/> + +</vector> diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 570a31e..21d5764 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,18 +1,43 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout 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" + android:orientation="vertical" tools:context=".ui.settings.SettingsFragment"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/button_save" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="0.7" + android:backgroundTint="@color/white" + android:drawableLeft="@drawable/ic_baseline_save_24" + android:text="@string/action_save_transactions" + android:textAlignment="textStart" + android:textColor="@color/black" /> + + <Spinner + android:id="@+id/spinner_excel_type" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="0.3" /> + </LinearLayout> + <Button - android:id="@+id/logout_button" + android:id="@+id/button_logout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textAlignment="textStart" - android:textColor="@color/design_default_color_error" android:backgroundTint="@color/white" android:drawableLeft="@drawable/ic_baseline_logout_24" android:drawableTint="@color/design_default_color_error" - android:text="@string/action_sign_out" /> -</FrameLayout> \ No newline at end of file + android:text="@string/action_sign_out" + android:textAlignment="textStart" + android:textColor="@color/design_default_color_error" /> + +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..df45958 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string-array name="excel_types"> + <item>XLS</item> + <item>XLSX</item> + </string-array> +</resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 311f02b..9db0660 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,4 +18,5 @@ <string name="log_out_success">Log out success</string> <string name="yes">Yes</string> <string name="no">No</string> + <string name="action_save_transactions">Save transaction</string> </resources> \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee85886..5dc08ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ retrofit = "2.11.0" okhttp3 = "4.12.0" annotation = "1.7.1" work = "2.9.0" +excelkt = "1.0.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -43,6 +44,7 @@ retrofit2-converter-gson = { group = "com.squareup.retrofit2", name = "converter okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" } androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } androidx-work-runtime = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" } +excelkt = { group = "io.github.evanrupert", name = "excelkt", version.ref = "excelkt" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } -- GitLab