Skip to content
Snippets Groups Projects
Commit 7a35a16f authored by Nathania Calista's avatar Nathania Calista
Browse files

Merge branch 'feat/send-to-gmail' into 'develop'

Feat/send to gmail

See merge request !16
parents 8856d217 5b50c658
Branches
Tags
2 merge requests!27Develop,!16Feat/send to gmail
...@@ -3,20 +3,7 @@ ...@@ -3,20 +3,7 @@
<component name="deploymentTargetDropDown"> <component name="deploymentTargetDropDown">
<value> <value>
<entry key="app"> <entry key="app">
<State> <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>
</entry> </entry>
<entry key="insert()"> <entry key="insert()">
<State /> <State />
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> 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-permission android:name="android.permission.INTERNET"/>
<uses-feature android:name="android.hardware.camera.any" /> <uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
...@@ -23,7 +21,6 @@ ...@@ -23,7 +21,6 @@
android:exported="true"> <!-- Make sure to set exported="true" --> android:exported="true"> <!-- Make sure to set exported="true" -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
...@@ -33,8 +30,21 @@ ...@@ -33,8 +30,21 @@
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.BondoMan.ActionBar"> 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> </activity>
<service android:name=".services.TokenCheckService"/> <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> </application>
</manifest> </manifest>
\ No newline at end of file
package com.example.bondoman.fragments package com.example.bondoman.fragments
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
...@@ -14,6 +19,7 @@ import com.example.bondoman.database.TransactionDatabase ...@@ -14,6 +19,7 @@ import com.example.bondoman.database.TransactionDatabase
import com.example.bondoman.databinding.FragmentSettingsBinding import com.example.bondoman.databinding.FragmentSettingsBinding
import com.example.bondoman.entities.Transaction import com.example.bondoman.entities.Transaction
import com.example.bondoman.lib.ITransactionFileAdapter import com.example.bondoman.lib.ITransactionFileAdapter
import com.example.bondoman.lib.SecurePreferences
import com.example.bondoman.lib.TransactionDownloader import com.example.bondoman.lib.TransactionDownloader
import com.example.bondoman.lib.TransactionExcelAdapter import com.example.bondoman.lib.TransactionExcelAdapter
import com.example.bondoman.repositories.TransactionRepository import com.example.bondoman.repositories.TransactionRepository
...@@ -25,6 +31,8 @@ import kotlinx.coroutines.Dispatchers ...@@ -25,6 +31,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
...@@ -35,15 +43,21 @@ class SettingsFragment : Fragment() { ...@@ -35,15 +43,21 @@ class SettingsFragment : Fragment() {
private val transactionViewModel: TransactionsViewModel by viewModels { private val transactionViewModel: TransactionsViewModel by viewModels {
TransactionViewModelFactory( TransactionViewModelFactory(
TransactionRepository( TransactionRepository(
TransactionDatabase.getInstance(requireContext(), CoroutineScope( TransactionDatabase.getInstance(
SupervisorJob() requireContext(),
) CoroutineScope(
).transactionDao()) SupervisorJob()
)
).transactionDao()
)
) )
} }
private lateinit var transactions: List<Transaction> private lateinit var transactions: List<Transaction>
private lateinit var transactionFileAdapter: ITransactionFileAdapter private lateinit var transactionFileAdapter: ITransactionFileAdapter
private lateinit var transactionDownloader: TransactionDownloader 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -52,6 +66,7 @@ class SettingsFragment : Fragment() { ...@@ -52,6 +66,7 @@ class SettingsFragment : Fragment() {
} }
transactionFileAdapter = TransactionExcelAdapter() transactionFileAdapter = TransactionExcelAdapter()
transactionDownloader = TransactionDownloader() transactionDownloader = TransactionDownloader()
securePreferences = SecurePreferences(requireContext())
} }
override fun onCreateView( override fun onCreateView(
...@@ -66,27 +81,10 @@ class SettingsFragment : Fragment() { ...@@ -66,27 +81,10 @@ class SettingsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.loadingAnimation.isVisible = false binding.loadingAnimation.isVisible = false
binding.saveButton.setOnClickListener { binding.saveButton.setOnClickListener {
Log.d("SettingsFragment", "Loading started") showSaveTransactionDialog()
binding.saveButton.isClickable = false }
showLoading() binding.sendButton.setOnClickListener {
val context = requireContext() handleSendButtonClick()
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
}
} }
} }
...@@ -98,23 +96,99 @@ class SettingsFragment : Fragment() { ...@@ -98,23 +96,99 @@ class SettingsFragment : Fragment() {
binding.loadingAnimation.isVisible = false binding.loadingAnimation.isVisible = false
} }
private fun createFileName(transactions: List<Transaction>): String { private fun createFileName(transactions: List<Transaction>, extension: String): String {
val dateFormat = SimpleDateFormat("dd MM yyyy HH:mm:ss:SSS", Locale.getDefault()) val dateFormat = SimpleDateFormat("dd-MM-yyyy HH:mm:ss:SSS", Locale.getDefault())
val currentTime = dateFormat.format(Date()) val currentTime = dateFormat.format(Date())
val userEmail = transactions.getOrNull(0)?.userEmail ?: "UnknownUser" val userEmail = transactions.getOrNull(0)?.userEmail ?: "UnknownUser"
val userName = userEmail.split("@").firstOrNull() ?: "UnknownUser" val userName = userEmail.split("@").firstOrNull() ?: "UnknownUser"
val fileName = "$currentTime $userName Transaction Summary" val fileName = "$currentTime $userName Transaction Summary"
return "$fileName.xlsx" return "$fileName.$extension"
} }
private fun showSnackbar(message: String) { private fun showSnackbar(message: String) {
Snackbar Snackbar
.make(binding.snackbarContainer, message, Snackbar.LENGTH_INDEFINITE) .make(binding.snackbarContainer, message, 5000)
.setAction("OK") {} .setAction("OK") {}
.show() .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
...@@ -2,6 +2,7 @@ package com.example.bondoman.lib ...@@ -2,6 +2,7 @@ package com.example.bondoman.lib
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
...@@ -16,9 +17,8 @@ class TransactionDownloader { ...@@ -16,9 +17,8 @@ class TransactionDownloader {
fileName: String, fileName: String,
transactions: List<Transaction>, transactions: List<Transaction>,
mimeType: String, mimeType: String,
transactionFileAdapter: ITransactionFileAdapter) { transactionFileAdapter: ITransactionFileAdapter): Uri? {
withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType) put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
...@@ -30,13 +30,15 @@ class TransactionDownloader { ...@@ -30,13 +30,15 @@ class TransactionDownloader {
if (uri != null) { if (uri != null) {
try { try {
resolver.openOutputStream(uri).use { outputStream -> resolver.openOutputStream(uri).use { outputStream ->
transactionFileAdapter.save(transactions, "love.xlsx", outputStream!!) transactionFileAdapter.save(transactions, fileName, outputStream!!)
} }
Log.d("File saved", "$fileName is saved successfully") Log.d("File saved", "$fileName is saved successfully")
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }
} }
Log.d("TransactionDownloader", uri.toString())
return@withContext uri
} }
} }
} }
\ No newline at end of file
...@@ -21,27 +21,28 @@ class TransactionExcelAdapter: ITransactionFileAdapter { ...@@ -21,27 +21,28 @@ class TransactionExcelAdapter: ITransactionFileAdapter {
val columnNames = sheet.createRow(0) val columnNames = sheet.createRow(0)
columnNames.createCell(0).setCellValue("id") columnNames.createCell(0).setCellValue("id")
columnNames.createCell(1).setCellValue("title") columnNames.createCell(1).setCellValue("date")
columnNames.createCell(2).setCellValue("category") columnNames.createCell(2).setCellValue("title")
columnNames.createCell(3).setCellValue("amount") columnNames.createCell(3).setCellValue("category")
columnNames.createCell(4).setCellValue("location") columnNames.createCell(4).setCellValue("amount")
columnNames.createCell(5).setCellValue("location")
for ((index, transaction) in transactions.withIndex()) { for ((index, transaction) in transactions.withIndex()) {
val row = sheet.createRow(index + 1) val row = sheet.createRow(index + 1)
val idCell = row.createCell(0) val idCell = row.createCell(0)
idCell.setCellValue(transaction.id.toDouble()) 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) titleCell.setCellValue(transaction.title)
val categoryCell = row.createCell(2) val categoryCell = row.createCell(3)
categoryCell.setCellValue(transaction.category) categoryCell.setCellValue(transaction.category)
val amountCell = row.createCell(3) val amountCell = row.createCell(4)
amountCell.setCellValue(transaction.amount.toDouble()) amountCell.setCellValue(transaction.amount.toDouble())
val locationCell = row.createCell(4) val locationCell = row.createCell(5)
locationCell.setCellValue(transaction.location) locationCell.setCellValue(transaction.location)
} }
// val fos = FileOutputStream(fileName)
workbook.write(outputStream) workbook.write(outputStream)
} }
} }
\ No newline at end of file
...@@ -6,5 +6,6 @@ ...@@ -6,5 +6,6 @@
<string name="send_button_text">Kirim daftar transaksi</string> <string name="send_button_text">Kirim daftar transaksi</string>
<string name="logout_button_text">Keluar</string> <string name="logout_button_text">Keluar</string>
<string name="data_transaksi_title">Data Transaksi</string> <string name="data_transaksi_title">Data Transaksi</string>
<string name="randomize_buton_text">Randomize transaksi</string>
<string name="items">Items</string> <string name="items">Items</string>
</resources> </resources>
\ No newline at end of file
<?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
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment