Added the libraries to create PDF and CSV files directly from the app. Verify that the BiTSy folder exists in the external storage and if it doesn't then it creates it. Generate the filtered transactions' PDF with 7 columns: From, To, Memo, Date, Time, Asset Amount, and Fiat Equivalent.
This commit is contained in:
parent
b0e909072f
commit
d7c728db2f
4 changed files with 151 additions and 1 deletions
|
@ -103,6 +103,9 @@ dependencies {
|
|||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.google.firebase:firebase-crash:16.2.1'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
|
||||
// PDF and CSV generation
|
||||
implementation 'com.itextpdf:itextpdf:5.5.13'
|
||||
implementation 'com.opencsv:opencsv:3.7'
|
||||
// Others
|
||||
implementation 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||
implementation 'com.moldedbits.r2d2:r2d2:1.0.1'
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.Manifest
|
|||
import android.content.pm.PackageManager
|
||||
import android.graphics.Point
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.*
|
||||
import androidx.appcompat.widget.SearchView
|
||||
|
@ -21,11 +22,13 @@ import cy.agorise.bitsybitshareswallet.adapters.TransfersDetailsAdapter
|
|||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import cy.agorise.bitsybitshareswallet.utils.BounceTouchListener
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.PDFGeneratorTask
|
||||
import cy.agorise.bitsybitshareswallet.utils.toast
|
||||
import cy.agorise.bitsybitshareswallet.viewmodels.TransferDetailViewModel
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.fragment_transactions.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
|
@ -265,8 +268,17 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
}
|
||||
}
|
||||
|
||||
/** Created the export procedures for PDF and CSV, depending on the user selection. */
|
||||
/** Creates the export procedures for PDF and CSV, depending on the user selection. */
|
||||
private fun exportFilteredTransactions(exportPDF: Boolean, exportCSV: Boolean) {
|
||||
// Verifies the BiTSy folder exists in the external storage and if it doesn't then it tries to create it
|
||||
val dir = File(Environment.getExternalStorageDirectory(), Constants.EXTERNAL_STORAGE_FOLDER)
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs())
|
||||
return
|
||||
}
|
||||
|
||||
if (exportPDF)
|
||||
activity?.let { PDFGeneratorTask(it).execute(filteredTransfersDetails) }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -95,4 +95,7 @@ object Constants {
|
|||
|
||||
/** Constant used to decide whether or not to update the tellers and merchants info from the webservice */
|
||||
const val MERCHANTS_UPDATE_PERIOD = 1000L * 60 * 60 + 24 // 1 day
|
||||
|
||||
/** Name of the external storage folder used to save files like PDF and CSV exports and Backups **/
|
||||
const val EXTERNAL_STORAGE_FOLDER = "BiTSy"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package cy.agorise.bitsybitshareswallet.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import com.itextpdf.text.Document
|
||||
import com.itextpdf.text.PageSize
|
||||
import com.itextpdf.text.Paragraph
|
||||
import com.itextpdf.text.pdf.PdfPCell
|
||||
import com.itextpdf.text.pdf.PdfPTable
|
||||
import com.itextpdf.text.pdf.PdfWriter
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.lang.Exception
|
||||
import java.lang.ref.WeakReference
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* AsyncTask subclass used to move the PDF generation procedure to a background thread
|
||||
* and inform the UI of the progress.
|
||||
*/
|
||||
class PDFGeneratorTask(context: Context) : AsyncTask<List<TransferDetail>, Int, String>() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PDFGeneratorTask"
|
||||
}
|
||||
|
||||
private val mContext: WeakReference<Context> = WeakReference(context)
|
||||
|
||||
override fun doInBackground(vararg params: List<TransferDetail>): String {
|
||||
return createPDFDocument(params[0])
|
||||
}
|
||||
|
||||
private fun createPDFDocument(transferDetails: List<TransferDetail>): String {
|
||||
val document = Document(PageSize.A4.rotate())
|
||||
return try {
|
||||
// Create and configure a new PDF file to save the transfers list
|
||||
val externalStorageFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
Constants.EXTERNAL_STORAGE_FOLDER
|
||||
val fileName = mContext.get()?.resources?.let {
|
||||
"${it.getString(R.string.app_name)}-${it.getString(R.string.title_transactions)}"} + ".pdf"
|
||||
val filePath = combinePath(externalStorageFolder, fileName)
|
||||
createEmptyFile(filePath)
|
||||
PdfWriter.getInstance(document, FileOutputStream(filePath))
|
||||
document.open()
|
||||
|
||||
// Configure pdf table with 8 columns
|
||||
val table = PdfPTable(7)
|
||||
|
||||
// Add the table header
|
||||
val columnNames = arrayOf("From", "To", "Memo", "Date", "Time", "Asset Amount", "Fiat Equivalent")
|
||||
for (columnName in columnNames) {
|
||||
val cell = PdfPCell(Paragraph(columnName))
|
||||
table.addCell(cell)
|
||||
}
|
||||
table.completeRow()
|
||||
|
||||
// Configure date and time formats to reuse in all the transfers
|
||||
val locale = mContext.get()?.resources?.configuration?.locale
|
||||
val calendar = Calendar.getInstance()
|
||||
val dateFormat = SimpleDateFormat("MM-dd-yyyy", locale)
|
||||
val timeFormat = SimpleDateFormat("HH:MM:ss", locale)
|
||||
|
||||
// Save all the transfers information
|
||||
for ( (index, transferDetail) in transferDetails.withIndex()) {
|
||||
calendar.timeInMillis = transferDetail.date * 1000
|
||||
val date = calendar.time
|
||||
|
||||
table.addCell(makeCell(transferDetail.from ?: "")) // From
|
||||
table.addCell(makeCell(transferDetail.to ?: "")) // To
|
||||
table.addCell(makeCell(transferDetail.memo)) // Memo
|
||||
table.addCell(makeCell(dateFormat.format(date))) // Date
|
||||
table.addCell(makeCell(timeFormat.format(date))) // Time
|
||||
|
||||
// Asset Amount
|
||||
val assetPrecision = transferDetail.assetPrecision
|
||||
val assetAmount = transferDetail.assetAmount.toDouble() / Math.pow(10.0, assetPrecision.toDouble())
|
||||
table.addCell(makeCell(String.format("%.${assetPrecision}f %s", assetAmount, transferDetail.assetSymbol)))
|
||||
|
||||
// Fiat Equivalent TODO add once Nelson finishes
|
||||
|
||||
table.completeRow()
|
||||
|
||||
// TODO update progress
|
||||
}
|
||||
document.add(table)
|
||||
document.close()
|
||||
"PDF generated and saved: $filePath"
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception while trying to generate a PDF. Msg: " + e.message)
|
||||
"Unable to generate PDF. Please retry. Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun combinePath(path1: String, path2: String): String {
|
||||
val file1 = File(path1)
|
||||
val file2 = File(file1, path2)
|
||||
return file2.path
|
||||
}
|
||||
|
||||
/** Creates an empty file with the given name, in case it does not exist */
|
||||
private fun createEmptyFile(path: String) {
|
||||
try {
|
||||
val file = File(path)
|
||||
val writer = FileWriter(file)
|
||||
writer.flush()
|
||||
writer.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Hides the simple but repetitive logic of creating a PDF table cell */
|
||||
private fun makeCell(text: String) : PdfPCell {
|
||||
return PdfPCell(Paragraph(text))
|
||||
}
|
||||
|
||||
override fun onProgressUpdate(values: Array<Int>) {
|
||||
// TODO show progress
|
||||
}
|
||||
|
||||
override fun onPostExecute(message: String) {
|
||||
mContext.get()?.toast(message)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue