diff --git a/app/src/main/java/com/example/abe/MainActivity.kt b/app/src/main/java/com/example/abe/MainActivity.kt index bd2566f312523d89a3ba30bff8327ff5069e9ad9..2b23e9a0ea33fcc348575165fcbed005f1756634 100644 --- a/app/src/main/java/com/example/abe/MainActivity.kt +++ b/app/src/main/java/com/example/abe/MainActivity.kt @@ -3,18 +3,21 @@ package com.example.abe import android.app.Activity import android.content.Intent import android.os.Bundle -import android.util.Log import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import com.example.abe.databinding.ActivityMainBinding import com.example.abe.ui.transactions.ExportAlertDialogFragment +import com.example.abe.ui.transactions.ExportAlertDialogTypeEnum +import com.example.abe.ui.transactions.ExportLoadDialogFragment import com.google.android.material.bottomnavigation.BottomNavigationView +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity(), ExportAlertDialogFragment.ExportAlertDialogListener { @@ -33,39 +36,76 @@ class MainActivity : AppCompatActivity(), ExportAlertDialogFragment.ExportAlertD val navController = findNavController(R.id.nav_host_fragment_activity_main) - val appBarConfiguration = AppBarConfiguration(setOf( - R.id.navigation_transactions)) + val appBarConfiguration = AppBarConfiguration( + setOf( + R.id.navigation_transactions + ) + ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) } - override fun onNewExcelFormatClick(dialog: DialogFragment) { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - putExtra(Intent.EXTRA_TITLE, viewModel.getExportFileName()) - viewModel.newExcelFormat = true + override fun onNewExcelFormatClick(dialog: DialogFragment, type: ExportAlertDialogTypeEnum) { + viewModel.newExcelFormat = true + when (type) { + ExportAlertDialogTypeEnum.EXPORT -> { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + putExtra(Intent.EXTRA_TITLE, viewModel.getExportFileName()) + } + resultLauncher.launch(intent) + } + + ExportAlertDialogTypeEnum.SEND_EMAIL -> { + sendEmail() + } } - resultLauncher.launch(intent) } - override fun onOldExcelFormatClick(dialog: DialogFragment) { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - setType("application/vnd.ms-excel") - putExtra(Intent.EXTRA_TITLE, viewModel.getExportFileName()) - viewModel.newExcelFormat = false + override fun onOldExcelFormatClick(dialog: DialogFragment, type: ExportAlertDialogTypeEnum) { + viewModel.newExcelFormat = false + when (type) { + ExportAlertDialogTypeEnum.EXPORT -> { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + setType("application/vnd.ms-excel") + putExtra(Intent.EXTRA_TITLE, viewModel.getExportFileName()) + } + resultLauncher.launch(intent) + } + + ExportAlertDialogTypeEnum.SEND_EMAIL -> { + sendEmail() + } } - resultLauncher.launch(intent) } - private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val data: Intent? = result.data - data?.data?.also {uri -> - viewModel.exportTransactionsToExcel( - applicationContext.contentResolver, uri) + private fun sendEmail() { + lifecycleScope.launch { + val exportLoadDialog = ExportLoadDialogFragment() + + exportLoadDialog.show(supportFragmentManager, "LOAD_DIALOG") + val intent = viewModel.createEmailIntent(applicationContext) + exportLoadDialog.dismiss() + + if (intent.resolveActivity(packageManager) != null) { + startActivity(Intent.createChooser(intent, "Choose email app..")) } } } + + private var resultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val data: Intent? = result.data + data?.data?.also { uri -> + lifecycleScope.launch { + viewModel.exportTransactionsToExcel( + applicationContext.contentResolver, uri + ) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/abe/MainActivityViewModel.kt b/app/src/main/java/com/example/abe/MainActivityViewModel.kt index ecb143ad5f294243fb29e6240aa47fdde2aaf97e..10860c081fb0a7dbd90fa400127a1393c496743b 100644 --- a/app/src/main/java/com/example/abe/MainActivityViewModel.kt +++ b/app/src/main/java/com/example/abe/MainActivityViewModel.kt @@ -1,13 +1,16 @@ package com.example.abe import android.content.ContentResolver +import android.content.Context +import android.content.Intent import android.net.Uri +import androidx.core.content.FileProvider import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.example.abe.data.TransactionRepository import com.example.abe.domain.FormatCurrencyUseCase import com.example.abe.domain.GenerateExcelUseCase -import com.example.abe.ui.transactions.TransactionViewModel +import java.io.File import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -20,7 +23,7 @@ class MainActivityViewModel(private val transactionRepository: TransactionReposi return "Daftar-Transaksi_$date" } - fun exportTransactionsToExcel(contentResolver: ContentResolver, uri: Uri) { + suspend fun exportTransactionsToExcel(contentResolver: ContentResolver, uri: Uri) { val headerList = listOf("ID Transaksi", "Email", "Judul", "Nominal", "Pengeluaran", "Waktu Transasksi") val transactions = transactionRepository.allTransaction.value val dataList = mutableListOf<List<String>>() @@ -43,6 +46,35 @@ class MainActivityViewModel(private val transactionRepository: TransactionReposi val generateExcel = GenerateExcelUseCase(newExcelFormat, contentResolver, uri, "Transaksi", headerList, dataList) generateExcel() } + + suspend fun createEmailIntent(context: Context): Intent { + clearExportCacheFiles(context) + val newFile = File(context.externalCacheDir, if (newExcelFormat) "export.xlsx" else "export.xls") + val contentUri = + FileProvider.getUriForFile(context, "com.example.abe.fileprovider", newFile) + exportTransactionsToExcel(context.contentResolver, contentUri) + + val intent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_EMAIL, arrayOf("13521134@std.stei.itb.ac.id")) + putExtra(Intent.EXTRA_SUBJECT, "test subject") + + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + setDataAndType(contentUri, context.contentResolver.getType(contentUri)) + putExtra(Intent.EXTRA_STREAM, contentUri) + } + + return intent + } + + fun clearExportCacheFiles(context: Context) { + context.externalCacheDir?.apply { + val files = listFiles() ?: emptyArray() + files.forEach { file -> + if (file.name.startsWith("export")) + file.delete() + } + } + } } class MainActivityViewModelFactory(private val repository: TransactionRepository): ViewModelProvider.Factory { diff --git a/app/src/main/java/com/example/abe/domain/GenerateExcelUseCase.kt b/app/src/main/java/com/example/abe/domain/GenerateExcelUseCase.kt index 357dff8a784172d1f4917e6a764bf3ce076e3af9..7764d3cd64172152c8153ee919b8b52ae57aa278 100644 --- a/app/src/main/java/com/example/abe/domain/GenerateExcelUseCase.kt +++ b/app/src/main/java/com/example/abe/domain/GenerateExcelUseCase.kt @@ -3,9 +3,9 @@ package com.example.abe.domain import android.content.ContentResolver import android.net.Uri import android.util.Log -import org.apache.poi.hssf.usermodel.HSSFCellStyle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.apache.poi.hssf.usermodel.HSSFWorkbook -import org.apache.poi.hssf.util.HSSFColor import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.FillPatternType import org.apache.poi.ss.usermodel.IndexedColors @@ -15,10 +15,6 @@ import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.xssf.usermodel.IndexedColorMap import org.apache.poi.xssf.usermodel.XSSFColor import org.apache.poi.xssf.usermodel.XSSFWorkbook -import java.io.File -import java.io.FileOutputStream -import java.net.URI -import kotlin.io.path.toPath class GenerateExcelUseCase( private val newFormat: Boolean, @@ -28,20 +24,25 @@ class GenerateExcelUseCase( private val headerList: List<String>, private val dataList: List<List<String>> ) { - operator fun invoke() { - val workbook = createWorkbook() - - try { - val outputStream = contentResolver.openOutputStream(uri) - workbook.write(outputStream) - outputStream?.close() - } catch (e: Exception) { - e.printStackTrace() + suspend operator fun invoke() { + withContext(Dispatchers.Default) { + val workbook = createWorkbook() + + withContext(Dispatchers.IO) { + try { + val outputStream = contentResolver.openOutputStream(uri) + workbook.write(outputStream) + outputStream?.close() + Log.d("ABE-EXPORT", "Finish writing file") + } catch (e: Exception) { + e.printStackTrace() + } + } } } private fun createWorkbook(): Workbook { - val workbook = if(newFormat) XSSFWorkbook() else HSSFWorkbook() + val workbook = if (newFormat) XSSFWorkbook() else HSSFWorkbook() val sheet: Sheet = workbook.createSheet(sheetName) val cellStyle = getHeaderStyle(workbook) @@ -70,7 +71,7 @@ class GenerateExcelUseCase( val cellStyle: CellStyle = workbook.createCellStyle() val colorMap: IndexedColorMap = (workbook as XSSFWorkbook).stylesSource.indexedColors - var color = XSSFColor(IndexedColors.LIGHT_CORNFLOWER_BLUE, colorMap).indexed + val color = XSSFColor(IndexedColors.LIGHT_CORNFLOWER_BLUE, colorMap).indexed cellStyle.fillForegroundColor = color cellStyle.fillPattern = FillPatternType.SOLID_FOREGROUND @@ -85,7 +86,7 @@ class GenerateExcelUseCase( for ((colIndex, cellData) in rowList.withIndex()) { createCell(row, colIndex, cellData) } - } + } } private fun createCell(row: Row, columnIndex: Int, value: String?) { diff --git a/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogFragment.kt b/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogFragment.kt index 5d80dfc237a8b9c55e3767b20aa7af3e1dd11cf8..5055babbecb4ef463fd94cda93903296d4a88efd 100644 --- a/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogFragment.kt +++ b/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogFragment.kt @@ -3,16 +3,33 @@ package com.example.abe.ui.transactions import android.app.AlertDialog import android.app.Dialog import android.content.Context -import android.content.DialogInterface import android.os.Bundle import androidx.fragment.app.DialogFragment -class ExportAlertDialogFragment: DialogFragment() { - internal lateinit var listener: ExportAlertDialogListener +class ExportAlertDialogFragment : DialogFragment() { + private lateinit var listener: ExportAlertDialogListener + private lateinit var type: ExportAlertDialogTypeEnum + + companion object { + private const val TYPE = "type" + + fun newInstance( + type: ExportAlertDialogTypeEnum + ): ExportAlertDialogFragment = ExportAlertDialogFragment().apply { + arguments = Bundle().apply { + putString(TYPE, type.toString()) + } + } + } interface ExportAlertDialogListener { - fun onNewExcelFormatClick(dialog: DialogFragment) - fun onOldExcelFormatClick(dialog: DialogFragment) + fun onNewExcelFormatClick(dialog: DialogFragment, type: ExportAlertDialogTypeEnum) + fun onOldExcelFormatClick(dialog: DialogFragment, type: ExportAlertDialogTypeEnum) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + type = ExportAlertDialogTypeEnum.valueOf(arguments?.getString(TYPE) ?: "EXPORT") } override fun onAttach(context: Context) { @@ -20,8 +37,10 @@ class ExportAlertDialogFragment: DialogFragment() { try { listener = context as ExportAlertDialogListener } catch (e: ClassCastException) { - throw ClassCastException((context.toString() + - " must implement NoticeDialogListener")) + throw ClassCastException( + (context.toString() + + " must implement NoticeDialogListener") + ) } } @@ -30,10 +49,10 @@ class ExportAlertDialogFragment: DialogFragment() { val builder = AlertDialog.Builder(it) builder.setMessage("Choose Excel File Format") .setPositiveButton("Xlxs") { dialog, id -> - listener.onNewExcelFormatClick(this) + listener.onNewExcelFormatClick(this, type) } .setNegativeButton("Xls") { dialog, id -> - listener.onOldExcelFormatClick(this) + listener.onOldExcelFormatClick(this, type) } builder.create() } ?: throw IllegalStateException("Activity cannot be null") diff --git a/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogTypeEnum.kt b/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogTypeEnum.kt new file mode 100644 index 0000000000000000000000000000000000000000..7812b2bfed3315afd441a6b9dc8ed78341ee9d1a --- /dev/null +++ b/app/src/main/java/com/example/abe/ui/transactions/ExportAlertDialogTypeEnum.kt @@ -0,0 +1,6 @@ +package com.example.abe.ui.transactions + +enum class ExportAlertDialogTypeEnum { + SEND_EMAIL, + EXPORT +} \ No newline at end of file diff --git a/app/src/main/java/com/example/abe/ui/transactions/ExportLoadDialogFragment.kt b/app/src/main/java/com/example/abe/ui/transactions/ExportLoadDialogFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..71a0d4d7e13a1047a0c2dccb840dbc84a5f2ff2c --- /dev/null +++ b/app/src/main/java/com/example/abe/ui/transactions/ExportLoadDialogFragment.kt @@ -0,0 +1,20 @@ +package com.example.abe.ui.transactions + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.example.abe.R + +class ExportLoadDialogFragment: DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + isCancelable = false + return activity?.let { + val builder = AlertDialog.Builder(it) + + val inflater = requireActivity().layoutInflater + builder.setView(inflater.inflate(R.layout.dialog_load_excel, null)) + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/abe/ui/transactions/TransactionFragment.kt b/app/src/main/java/com/example/abe/ui/transactions/TransactionFragment.kt index 684ccf47ddfe62187e1a609dba5a1ea4f3697961..ddb0c363f660e446a7a14e4895edbfd331c01c68 100644 --- a/app/src/main/java/com/example/abe/ui/transactions/TransactionFragment.kt +++ b/app/src/main/java/com/example/abe/ui/transactions/TransactionFragment.kt @@ -1,6 +1,5 @@ package com.example.abe.ui.transactions -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -38,30 +37,20 @@ class TransactionFragment : Fragment() { binding.rvTransactions.adapter = transactionsAdapter binding.rvTransactions.layoutManager = LinearLayoutManager(context) - viewModel.allTransactions.observe(viewLifecycleOwner) {transactions -> + viewModel.allTransactions.observe(viewLifecycleOwner) { transactions -> transactions?.let { transactionsAdapter.submitList(it) } } binding.fabExport.setOnClickListener { - ExportAlertDialogFragment().show(requireActivity().supportFragmentManager, "EXPORT_DIALOG") + ExportAlertDialogFragment.newInstance(ExportAlertDialogTypeEnum.EXPORT) + .show(requireActivity().supportFragmentManager, "EXPORT_DIALOG") } binding.fabEmail.setOnClickListener { - val contentUri = viewModel.generateExcelInCache(requireActivity().applicationContext) - - val intent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_EMAIL, arrayOf("13521134@std.stei.itb.ac.id")) - putExtra(Intent.EXTRA_SUBJECT, "test subject") - - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - setDataAndType(contentUri, requireContext().contentResolver.getType(contentUri)) - putExtra(Intent.EXTRA_STREAM, contentUri) - } - if (intent.resolveActivity(requireActivity().packageManager) != null) { - startActivity(intent) - } + ExportAlertDialogFragment.newInstance(ExportAlertDialogTypeEnum.SEND_EMAIL) + .show(requireActivity().supportFragmentManager, "EXPORT_DIALOG") } return binding.root diff --git a/app/src/main/java/com/example/abe/ui/transactions/TransactionViewModel.kt b/app/src/main/java/com/example/abe/ui/transactions/TransactionViewModel.kt index 728824e7ecfedc59797c335ea9abbffe98e6dd6b..3e0b8126cb562afdd6d9a91b1f59fdf733afbe91 100644 --- a/app/src/main/java/com/example/abe/ui/transactions/TransactionViewModel.kt +++ b/app/src/main/java/com/example/abe/ui/transactions/TransactionViewModel.kt @@ -1,53 +1,17 @@ package com.example.abe.ui.transactions -import android.content.Context -import android.net.Uri -import androidx.core.content.FileProvider import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.example.abe.data.TransactionRepository -import com.example.abe.domain.FormatCurrencyUseCase -import com.example.abe.domain.GenerateExcelUseCase -import java.io.File -import java.text.SimpleDateFormat -import java.util.Locale class TransactionViewModel(private val transactionRepository: TransactionRepository) : ViewModel() { val allTransactions = transactionRepository.allTransaction - - fun generateExcelInCache(context: Context): Uri { - val headerList = listOf("ID Transaksi", "Email", "Judul", "Nominal", "Pengeluaran", "Waktu Transasksi") - val transactions = transactionRepository.allTransaction.value - val dataList = mutableListOf<List<String>>() - val currencyFormatter = FormatCurrencyUseCase() - - if (transactions != null) { - for (trx in transactions) { - val rowData = listOf<String>( - trx.id.toString(), - trx.email, - trx.title, - currencyFormatter(trx.amount), - if (trx.isExpense) "Ya" else "Tidak", - SimpleDateFormat("d MMM yyyy" , Locale.ENGLISH).format(trx.timestamp) - ) - dataList.add(rowData) - } - } - - val newFile = File(context.externalCacheDir, "export.xlsx") - val contentUri = - FileProvider.getUriForFile(context, "com.example.abe.fileprovider", newFile) - val generateExcel = GenerateExcelUseCase(true, context.contentResolver, contentUri, "Transaksi", headerList, dataList) - generateExcel() - - return contentUri - } } -class TransactionViewModelFactory(private val repository: TransactionRepository): ViewModelProvider.Factory { +class TransactionViewModelFactory(private val repository: TransactionRepository) : + ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(TransactionViewModel::class.java)) { @Suppress("UNCHECKED_CAST") diff --git a/app/src/main/res/layout/dialog_load_excel.xml b/app/src/main/res/layout/dialog_load_excel.xml new file mode 100644 index 0000000000000000000000000000000000000000..51649f4b189d4c7af56fa928cd6a0e0265a37395 --- /dev/null +++ b/app/src/main/res/layout/dialog_load_excel.xml @@ -0,0 +1,30 @@ +<?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" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="24dp"> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Loading File Attachment" + android:textColor="@android:color/black" + android:textSize="16sp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.progressindicator.CircularProgressIndicator + android:layout_width="64dp" + android:layout_height="62dp" + android:layout_marginTop="24dp" + android:indeterminate="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textView" + app:trackThickness="5dp" + app:indicatorSize="50dp"/> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file