diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index c11e845d4322a80c7b89b5ac0a6c913383bd0b10..4728463e69cb12388cbc07b7ceda807f9ec0c725 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -3,20 +3,7 @@ <component name="deploymentTargetDropDown"> <value> <entry key="app"> - <State> - <targetSelectedWithDropDown> - <Target> - <type value="QUICK_BOOT_TARGET" /> - <deviceKey> - <Key> - <type value="VIRTUAL_DEVICE_PATH" /> - <value value="$USER_HOME$/.android/avd/Pixel_5_API_34.avd" /> - </Key> - </deviceKey> - </Target> - </targetSelectedWithDropDown> - <timeTargetWasSelectedWithDropDown value="2024-03-20T06:11:55.691039Z" /> - </State> + <State /> </entry> <entry key="insert()"> <State /> diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 735291997a34a0582d02c1a08935d59f88e46f77..0105dde7a17b5881fa13bfc0fb72289e6bdaba37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> - <uses-feature android:name="android.hardware.camera.any" /> - <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.CAMERA" /> @@ -23,7 +21,6 @@ android:exported="true"> <!-- Make sure to set exported="true" --> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> @@ -33,8 +30,21 @@ android:name=".activities.MainActivity" android:exported="true" android:theme="@style/Theme.BondoMan.ActionBar"> + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> </activity> <service android:name=".services.TokenCheckService"/> + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.provider" + android:grantUriPermissions="true" + android:exported="false"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/provider_paths" /> + </provider> </application> - </manifest> \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/fragments/SettingsFragment.kt b/app/src/main/java/com/example/bondoman/fragments/SettingsFragment.kt index 72f6e9bea47a4c71b23d6c3f68dd470038bb2920..4580da3d507fc980ba9498642705d4bfaaa7aba4 100644 --- a/app/src/main/java/com/example/bondoman/fragments/SettingsFragment.kt +++ b/app/src/main/java/com/example/bondoman/fragments/SettingsFragment.kt @@ -1,11 +1,16 @@ package com.example.bondoman.fragments +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.core.content.FileProvider +import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -14,6 +19,7 @@ import com.example.bondoman.database.TransactionDatabase import com.example.bondoman.databinding.FragmentSettingsBinding import com.example.bondoman.entities.Transaction import com.example.bondoman.lib.ITransactionFileAdapter +import com.example.bondoman.lib.SecurePreferences import com.example.bondoman.lib.TransactionDownloader import com.example.bondoman.lib.TransactionExcelAdapter import com.example.bondoman.repositories.TransactionRepository @@ -25,6 +31,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.launch +import java.io.File +import java.io.FileOutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -35,15 +43,21 @@ class SettingsFragment : Fragment() { private val transactionViewModel: TransactionsViewModel by viewModels { TransactionViewModelFactory( TransactionRepository( - TransactionDatabase.getInstance(requireContext(), CoroutineScope( - SupervisorJob() - ) - ).transactionDao()) + TransactionDatabase.getInstance( + requireContext(), + CoroutineScope( + SupervisorJob() + ) + ).transactionDao() + ) ) } private lateinit var transactions: List<Transaction> private lateinit var transactionFileAdapter: ITransactionFileAdapter private lateinit var transactionDownloader: TransactionDownloader + private lateinit var securePreferences: SecurePreferences + private val XLSX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + private val XLS_MIME_TYPE = "application/vnd.ms-excel" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -52,6 +66,7 @@ class SettingsFragment : Fragment() { } transactionFileAdapter = TransactionExcelAdapter() transactionDownloader = TransactionDownloader() + securePreferences = SecurePreferences(requireContext()) } override fun onCreateView( @@ -66,27 +81,10 @@ class SettingsFragment : Fragment() { super.onViewCreated(view, savedInstanceState) binding.loadingAnimation.isVisible = false binding.saveButton.setOnClickListener { - Log.d("SettingsFragment", "Loading started") - binding.saveButton.isClickable = false - showLoading() - val context = requireContext() - val fileName = createFileName(transactions) - this.lifecycleScope.launch { - val result = async(Dispatchers.IO) { - transactionDownloader.downloadTransactionAsFile( - context, - fileName, - transactions, - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - transactionFileAdapter - ) - } - result.await() - Log.d("SettingsFragment", "Loading finished") - hideLoading() - showSnackbar("Your transactions have been exported inside Download file") - binding.saveButton.isClickable = true - } + showSaveTransactionDialog() + } + binding.sendButton.setOnClickListener { + handleSendButtonClick() } } @@ -98,23 +96,99 @@ class SettingsFragment : Fragment() { binding.loadingAnimation.isVisible = false } - private fun createFileName(transactions: List<Transaction>): String { - val dateFormat = SimpleDateFormat("dd MM yyyy HH:mm:ss:SSS", Locale.getDefault()) + private fun createFileName(transactions: List<Transaction>, extension: String): String { + val dateFormat = SimpleDateFormat("dd-MM-yyyy HH:mm:ss:SSS", Locale.getDefault()) val currentTime = dateFormat.format(Date()) val userEmail = transactions.getOrNull(0)?.userEmail ?: "UnknownUser" val userName = userEmail.split("@").firstOrNull() ?: "UnknownUser" val fileName = "$currentTime $userName Transaction Summary" - return "$fileName.xlsx" + return "$fileName.$extension" } private fun showSnackbar(message: String) { Snackbar - .make(binding.snackbarContainer, message, Snackbar.LENGTH_INDEFINITE) + .make(binding.snackbarContainer, message, 5000) .setAction("OK") {} .show() } + private fun composeEmail(addresses: Array<String>, subject: String, text: String, attachment: Uri) { + val intent = Intent(Intent.ACTION_SEND) + intent.type = XLSX_MIME_TYPE + intent.putExtra(Intent.EXTRA_EMAIL, addresses) + intent.putExtra(Intent.EXTRA_SUBJECT, subject) + intent.putExtra(Intent.EXTRA_STREAM, attachment) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + + if (intent.resolveActivity(requireActivity().packageManager) != null) { + startActivity(intent) + } + } + + private fun saveTransaction(extension: String) { + this.lifecycleScope.launch { + binding.saveButton.isClickable = false + val context = requireContext() + val fileName = createFileName(transactions, extension) + val result = async(Dispatchers.IO) { + return@async transactionDownloader.downloadTransactionAsFile( + context, + fileName, + transactions, + if (extension == "xlsx") XLSX_MIME_TYPE else XLS_MIME_TYPE, + transactionFileAdapter + ) + } + Log.d("SettingsFragment", "Loading started") + showLoading() + result.await() + Log.d("SettingsFragment", "Loading finished") + hideLoading() + showSnackbar("Your transactions have been exported inside Download file") + binding.saveButton.isClickable = true + } + } + + private fun showSaveTransactionDialog() { + var extension: String = "xlsx" + val choiceItems = arrayOf("xlsx", "xls") + val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) + builder + .setTitle("Choose saved file format") + .setPositiveButton("OK") { dialog, _ -> + saveTransaction(extension) + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .setSingleChoiceItems( + choiceItems, 0 + ) { dialog, which -> + extension = choiceItems[which] + } + + val dialog: AlertDialog = builder.create() + dialog.show() + } + + private fun handleSendButtonClick() { + val context = requireContext() + val fileName = createFileName(transactions, "xlsx") + val file = File(requireContext().externalCacheDir, fileName) + val outputStream = FileOutputStream(file) + + outputStream.use { + transactionFileAdapter.save(transactions, fileName, it) + } + composeEmail( + arrayOf(securePreferences.getEmail()!!), + "Bondoman Transaction Summary", + "Here's your latest transaction summary", + FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", file) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/lib/TransactionDownloader.kt b/app/src/main/java/com/example/bondoman/lib/TransactionDownloader.kt index 5fd2a35237cf95865a8251e87835e4720dcf36bc..f733432b96a0646315d9a9bb9a7e2dbf9bd9c086 100644 --- a/app/src/main/java/com/example/bondoman/lib/TransactionDownloader.kt +++ b/app/src/main/java/com/example/bondoman/lib/TransactionDownloader.kt @@ -2,6 +2,7 @@ package com.example.bondoman.lib import android.content.ContentValues import android.content.Context +import android.net.Uri import android.os.Environment import android.provider.MediaStore import android.util.Log @@ -16,9 +17,8 @@ class TransactionDownloader { fileName: String, transactions: List<Transaction>, mimeType: String, - transactionFileAdapter: ITransactionFileAdapter) { - withContext(Dispatchers.IO) { - + transactionFileAdapter: ITransactionFileAdapter): Uri? { + return withContext(Dispatchers.IO) { val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) put(MediaStore.MediaColumns.MIME_TYPE, mimeType) @@ -30,13 +30,15 @@ class TransactionDownloader { if (uri != null) { try { resolver.openOutputStream(uri).use { outputStream -> - transactionFileAdapter.save(transactions, "love.xlsx", outputStream!!) + transactionFileAdapter.save(transactions, fileName, outputStream!!) } Log.d("File saved", "$fileName is saved successfully") } catch (e: IOException) { e.printStackTrace() } } + Log.d("TransactionDownloader", uri.toString()) + return@withContext uri } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/lib/TransactionExcelAdapter.kt b/app/src/main/java/com/example/bondoman/lib/TransactionExcelAdapter.kt index f4bb4cb455f5133432d3fd1863a1f9ac26b5f844..717e580330eeacca18a28186aec1c2b8ad945515 100644 --- a/app/src/main/java/com/example/bondoman/lib/TransactionExcelAdapter.kt +++ b/app/src/main/java/com/example/bondoman/lib/TransactionExcelAdapter.kt @@ -21,27 +21,28 @@ class TransactionExcelAdapter: ITransactionFileAdapter { val columnNames = sheet.createRow(0) columnNames.createCell(0).setCellValue("id") - columnNames.createCell(1).setCellValue("title") - columnNames.createCell(2).setCellValue("category") - columnNames.createCell(3).setCellValue("amount") - columnNames.createCell(4).setCellValue("location") - + columnNames.createCell(1).setCellValue("date") + columnNames.createCell(2).setCellValue("title") + columnNames.createCell(3).setCellValue("category") + columnNames.createCell(4).setCellValue("amount") + columnNames.createCell(5).setCellValue("location") for ((index, transaction) in transactions.withIndex()) { val row = sheet.createRow(index + 1) val idCell = row.createCell(0) idCell.setCellValue(transaction.id.toDouble()) - val titleCell = row.createCell(1) + val dateCell = row.createCell(1) + dateCell.setCellValue(transaction.date) + val titleCell = row.createCell(2) titleCell.setCellValue(transaction.title) - val categoryCell = row.createCell(2) + val categoryCell = row.createCell(3) categoryCell.setCellValue(transaction.category) - val amountCell = row.createCell(3) + val amountCell = row.createCell(4) amountCell.setCellValue(transaction.amount.toDouble()) - val locationCell = row.createCell(4) + val locationCell = row.createCell(5) locationCell.setCellValue(transaction.location) } -// val fos = FileOutputStream(fileName) workbook.write(outputStream) } } \ 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 0adeb3f342e1e04275d84857d3acaea4bc68b79f..0c3615a7a97471191cf041487b84eed409abb9cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,5 +6,6 @@ <string name="send_button_text">Kirim daftar transaksi</string> <string name="logout_button_text">Keluar</string> <string name="data_transaksi_title">Data Transaksi</string> + <string name="randomize_buton_text">Randomize transaksi</string> <string name="items">Items</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 0000000000000000000000000000000000000000..3197349cc8116bd1767d7cd3f7a27f9105cbeb2e --- /dev/null +++ b/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <external-path name="external_files" path="." /> +</paths> \ No newline at end of file