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:
Severiano Jaramillo 2019-02-05 10:50:40 -06:00
parent b0e909072f
commit d7c728db2f
4 changed files with 151 additions and 1 deletions

View file

@ -103,6 +103,9 @@ dependencies {
implementation 'com.google.firebase:firebase-core:16.0.6' implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.google.firebase:firebase-crash:16.2.1' implementation 'com.google.firebase:firebase-crash:16.2.1'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' 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 // Others
implementation 'org.bitcoinj:bitcoinj-core:0.14.3' implementation 'org.bitcoinj:bitcoinj-core:0.14.3'
implementation 'com.moldedbits.r2d2:r2d2:1.0.1' implementation 'com.moldedbits.r2d2:r2d2:1.0.1'

View file

@ -4,6 +4,7 @@ import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Point import android.graphics.Point
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.view.* import android.view.*
import androidx.appcompat.widget.SearchView 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.database.joins.TransferDetail
import cy.agorise.bitsybitshareswallet.utils.BounceTouchListener import cy.agorise.bitsybitshareswallet.utils.BounceTouchListener
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.PDFGeneratorTask
import cy.agorise.bitsybitshareswallet.utils.toast import cy.agorise.bitsybitshareswallet.utils.toast
import cy.agorise.bitsybitshareswallet.viewmodels.TransferDetailViewModel import cy.agorise.bitsybitshareswallet.viewmodels.TransferDetailViewModel
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.fragment_transactions.* import kotlinx.android.synthetic.main.fragment_transactions.*
import java.io.File
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList 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) { 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) }
} }

View file

@ -95,4 +95,7 @@ object Constants {
/** Constant used to decide whether or not to update the tellers and merchants info from the webservice */ /** 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 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"
} }

View file

@ -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)
}
}