diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 9cc81e63d1cf3099f2e26ffdd831d0cb30c4bcca..da2044165cb59ffaa8c8854ee599dbe7c7fad00b 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -4,18 +4,17 @@ <value> <entry key="app"> <State> - <targetSelectedWithDropDown> + <runningDeviceTargetsSelectedWithDialog> <Target> - <type value="QUICK_BOOT_TARGET" /> + <type value="RUNNING_DEVICE_TARGET" /> <deviceKey> <Key> - <type value="VIRTUAL_DEVICE_PATH" /> - <value value="C:\Users\dzafa\.android\avd\Pixel_7_Pro_API_34.avd" /> + <type value="SERIAL_NUMBER" /> + <value value="RR8R202MELK" /> </Key> </deviceKey> </Target> - </targetSelectedWithDropDown> - <timeTargetWasSelectedWithDropDown value="2024-04-01T08:55:20.631393400Z" /> + </runningDeviceTargetsSelectedWithDialog> </State> </entry> </value> diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6efccf1e3e7943600c92a5da175a0e05f5332baf..3342eafe518089a39c84b2f8430a3bcb17787c87 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -86,4 +86,8 @@ dependencies { implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") + val apache_poi_version = "5.2.5" + implementation ("org.apache.poi:poi:$apache_poi_version") + implementation ("org.apache.poi:poi-ooxml:$apache_poi_version") + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 951f3a0a53b8061febf51fdb4de278736d7c9599..f4d71c3fce0a22777d6bfc1a823ce43d419f10b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Always include this permission --> @@ -28,6 +29,17 @@ android:theme="@style/Theme.BondoMan" tools:targetApi="31" tools:ignore="ForegroundServicePermission"> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="com.example.bondoman.fileprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_paths" /> + </provider> + <service android:name="LocationService" android:foregroundServiceType="location" android:enabled="true" android:exported="false"/> <activity android:name=".MainActivity"/> <activity diff --git a/app/src/main/java/com/example/bondoman/MainActivity.kt b/app/src/main/java/com/example/bondoman/MainActivity.kt index 2b3190730f73d5ad0ef97cac00dcd9324aaa61d3..d4209da82001a39c9828a364f128937c2c39411f 100644 --- a/app/src/main/java/com/example/bondoman/MainActivity.kt +++ b/app/src/main/java/com/example/bondoman/MainActivity.kt @@ -26,7 +26,9 @@ import androidx.activity.viewModels import com.example.bondoman.models.TransactionViewModel import com.example.bondoman.room.BondomanDatabase import com.example.bondoman.services.LocationDefault +import com.example.bondoman.ui.chart.ChartViewModel import com.example.bondoman.ui.nointernet.NoInternetFragment +import com.example.bondoman.ui.settings.SettingsViewModel import com.google.android.gms.location.LocationServices import retrofit2.HttpException @@ -44,11 +46,21 @@ class MainActivity : AppCompatActivity() { TransactionViewModel.provideFactory(db.dao,LocationDefault(this,LocationServices.getFusedLocationProviderClient(this)),applicationContext,this) } + private val chartViewModel: ChartViewModel by viewModels { + ChartViewModel.provideFactory(db.dao,applicationContext,this) + } + + private val settingsViewModel: SettingsViewModel by viewModels { + SettingsViewModel.provideFactory(db.dao, applicationContext,this) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) locationPermission() d("OKE",viewModel.state.value.toString()) + d("DONTDELETE",chartViewModel.toString()) + d("DONTDELETE",settingsViewModel.state.value.toString()) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) replaceFragment(CartFragment(), NoInternetFragment(), "Cart") diff --git a/app/src/main/java/com/example/bondoman/models/Transaction.kt b/app/src/main/java/com/example/bondoman/models/Transaction.kt index 47fbb6a10a0bb840f6feb75783b4a825a994f4c8..e8b1dee87e091dd391de4fcd89728ed9e2d10ccb 100644 --- a/app/src/main/java/com/example/bondoman/models/Transaction.kt +++ b/app/src/main/java/com/example/bondoman/models/Transaction.kt @@ -31,6 +31,6 @@ data class Transaction( data class TransactionStats( val kategori: Transaction.Category, - val count: Int, - val totalNominal: Float + val total: Float, + val count: Float, ) diff --git a/app/src/main/java/com/example/bondoman/room/TransactionDao.kt b/app/src/main/java/com/example/bondoman/room/TransactionDao.kt index 668a6891d5c852dd1f2f256e7c8ad31eee2cce2c..8e946a4ce721960ae9690f3718f62be7923bcab0 100644 --- a/app/src/main/java/com/example/bondoman/room/TransactionDao.kt +++ b/app/src/main/java/com/example/bondoman/room/TransactionDao.kt @@ -33,7 +33,7 @@ interface TransactionDao { @Query("DELETE FROM `transaction`") suspend fun deleteAll() - @Query("SELECT kategori, COUNT(id) AS count, SUM(nominal) AS totalNominal FROM `transaction` GROUP BY kategori") + @Query("SELECT kategori, SUM(nominal) AS total, COUNT() as count FROM `transaction` GROUP BY kategori") fun getTransactionStats(): Flow<List<TransactionStats>> } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/chart/ChartEvent.kt b/app/src/main/java/com/example/bondoman/ui/chart/ChartEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..a0bc82958f90667787c9f2358d0da2974015c267 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/chart/ChartEvent.kt @@ -0,0 +1,5 @@ +package com.example.bondoman.ui.chart + +sealed interface ChartEvent { + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/chart/ChartFragment.kt b/app/src/main/java/com/example/bondoman/ui/chart/ChartFragment.kt index 714627195eb4916f53ab8ad5e382110d790f7f36..2e491f9107852a62e4c0c9b0903d0d9294977ade 100644 --- a/app/src/main/java/com/example/bondoman/ui/chart/ChartFragment.kt +++ b/app/src/main/java/com/example/bondoman/ui/chart/ChartFragment.kt @@ -2,23 +2,43 @@ package com.example.bondoman.ui.chart import android.graphics.Color import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.activityViewModels import com.example.bondoman.R import com.example.bondoman.databinding.FragmentChartBinding -import com.example.bondoman.databinding.FragmentLoginBinding +import com.example.bondoman.models.Transaction +import com.example.bondoman.models.TransactionStats import com.github.mikephil.charting.charts.PieChart import com.github.mikephil.charting.data.PieData import com.github.mikephil.charting.data.PieDataSet import com.github.mikephil.charting.data.PieEntry import com.github.mikephil.charting.utils.ColorTemplate +import android.util.Log.d +import androidx.lifecycle.lifecycleScope +import com.example.bondoman.models.TransactionViewModel +import com.example.bondoman.services.TransactionEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect class ChartFragment : Fragment() { private lateinit var binding: FragmentChartBinding - lateinit var pieChart: PieChart + private lateinit var pieChart: PieChart + + private lateinit var sortValueButton: Button + private lateinit var sortCountButton: Button + + private var transactionsStats : ArrayList<TransactionStats> = ArrayList() + + private val chartViewModel by activityViewModels<ChartViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -37,12 +57,44 @@ class ChartFragment : Fragment() { binding = FragmentChartBinding.bind(view) - var pieChart = binding.chart + pieChart = binding.chart + sortValueButton = binding.sortByValueButton + sortCountButton = binding.sortByCountButton + + d("GEDEBUGEMING", "breakpoint 1") + d("GEDEBUGEMING",chartViewModel.transactionStats.value.toString()) + d("GEDEBUGEMING", "breakpoint 10") + + runningBackgroundUpdateUI(true) + + sortValueButton.setOnClickListener{ + runningBackgroundUpdateUI(true) + } + + sortCountButton.setOnClickListener { + runningBackgroundUpdateUI(false) + } + } + private fun runningBackgroundUpdateUI(isSortedByValue: Boolean){ + CoroutineScope(Dispatchers.Main).launch { + chartViewModel.transactionStats.value.collect { stats -> + transactionsStats.clear() + transactionsStats.addAll(stats) + generateChart(transactionsStats,isSortedByValue) + } + } + } + private fun generateChart(transactionStats: List<TransactionStats>, isSortedByValue: Boolean){ val list:ArrayList<PieEntry> = ArrayList() - list.add(PieEntry(30f,"Pembelian")) - list.add(PieEntry(50f,"Penjualan")) + for(stats in transactionStats){ + if(isSortedByValue){ + list.add(PieEntry(stats.total, stats.kategori.toString())) + } else { + list.add(PieEntry(stats.count, stats.kategori.toString())) + } + } val pieDataSet= PieDataSet(list,"Chart") @@ -56,8 +108,12 @@ class ChartFragment : Fragment() { pieChart.description.text= "Pie Chart" - pieChart.centerText="List" + if(isSortedByValue){ + pieChart.centerText = "Sorted by Value" + } else { + pieChart.centerText = "Sorted by Count" + } - pieChart.animateY(2000) + pieChart.animateY(1000) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/chart/ChartState.kt b/app/src/main/java/com/example/bondoman/ui/chart/ChartState.kt new file mode 100644 index 0000000000000000000000000000000000000000..aa630cd937b51f738a5216473d26eaa9450642c3 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/chart/ChartState.kt @@ -0,0 +1,7 @@ +package com.example.bondoman.ui.chart + +import com.example.bondoman.models.TransactionStats + +data class ChartState( + val transactionStats : List<TransactionStats> = emptyList(), +) diff --git a/app/src/main/java/com/example/bondoman/ui/chart/ChartViewModel.kt b/app/src/main/java/com/example/bondoman/ui/chart/ChartViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..9f5a8e19d6287e72109e540ae4e1fd8e23d01848 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/chart/ChartViewModel.kt @@ -0,0 +1,50 @@ +package com.example.bondoman.ui.chart + +import android.content.Context +import android.os.Bundle +import androidx.lifecycle.AbstractSavedStateViewModelFactory +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.savedstate.SavedStateRegistryOwner +import com.example.bondoman.models.Transaction +import com.example.bondoman.models.TransactionStats +import com.example.bondoman.room.TransactionDao +import com.example.bondoman.services.LocationClient +import com.example.bondoman.services.TransactionState +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.stateIn + +@OptIn(ExperimentalCoroutinesApi::class) +class ChartViewModel(private val dao: TransactionDao, private val context: Context): ViewModel() { + private val _state = MutableStateFlow(ChartState()) + private val _transactionStats = MutableStateFlow(dao.getTransactionStats() ) + val transactionStats = _transactionStats.asStateFlow() + + // Define ViewModel factory in a companion object + companion object { + fun provideFactory( + dao: TransactionDao, + context: Context, + owner: SavedStateRegistryOwner, + defaultArgs: Bundle? = null, + ): AbstractSavedStateViewModelFactory = + object : AbstractSavedStateViewModelFactory(owner, defaultArgs) { + @Suppress("UNCHECKED_CAST") + override fun <T : ViewModel> create( + key: String, + modelClass: Class<T>, + handle: SavedStateHandle + ): T { + return ChartViewModel(dao, context) as T + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/settings/ExcelExtension.kt b/app/src/main/java/com/example/bondoman/ui/settings/ExcelExtension.kt new file mode 100644 index 0000000000000000000000000000000000000000..27b3f393393c55cb48e423acd600fa6ed80fc211 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/settings/ExcelExtension.kt @@ -0,0 +1,5 @@ +package com.example.bondoman.ui.settings + +enum class ExcelExtension { + XLSX, XLS +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/settings/SettingsEvent.kt b/app/src/main/java/com/example/bondoman/ui/settings/SettingsEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..346b5b69e7d7fadb564593d29ac85e09c2d36dca --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/settings/SettingsEvent.kt @@ -0,0 +1,5 @@ +package com.example.bondoman.ui.settings + +sealed interface SettingsEvent { + data class SetFileType(val fileType: ExcelExtension): SettingsEvent +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/settings/SettingsFragment.kt b/app/src/main/java/com/example/bondoman/ui/settings/SettingsFragment.kt index 86d783020f0a2b1d5938e5247cb54c81545717f7..1aa503e86a3714f7cd652e629469f4c3050e428d 100644 --- a/app/src/main/java/com/example/bondoman/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/example/bondoman/ui/settings/SettingsFragment.kt @@ -1,33 +1,59 @@ package com.example.bondoman.ui.settings +import android.Manifest +import android.content.Context +import android.content.Context.STORAGE_SERVICE import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build import android.os.Bundle -import androidx.fragment.app.Fragment +import android.os.Looper +import android.os.storage.StorageManager +import android.util.Log.d import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.CheckBox import android.widget.Toast -import com.example.bondoman.MainActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat.checkSelfPermission +import androidx.core.content.ContextCompat.getSystemService +import androidx.core.content.FileProvider +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import com.example.bondoman.LoginActivity import com.example.bondoman.R -import com.example.bondoman.databinding.FragmentChartBinding import com.example.bondoman.databinding.FragmentSettingsBinding +import com.example.bondoman.models.Transaction +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.apache.poi.hssf.usermodel.HSSFWorkbook +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale -// TODO: Rename parameter arguments, choose names that match -// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER -private const val ARG_PARAM1 = "param1" -private const val ARG_PARAM2 = "param2" -/** - * A simple [Fragment] subclass. - * Use the [SettingsFragment.newInstance] factory method to - * create an instance of this fragment. - */ class SettingsFragment : Fragment() { + private val settingsViewModel by activityViewModels<SettingsViewModel>() + private lateinit var binding: FragmentSettingsBinding private lateinit var saveButton: Button private lateinit var sendButton: Button private lateinit var logoutButton: Button + private lateinit var xlsxButton: CheckBox + private lateinit var xlsButton: CheckBox + + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -49,38 +75,199 @@ class SettingsFragment : Fragment() { saveButton = binding.settingsSaveButton sendButton = binding.settingSendButton logoutButton = binding.settingsLogoutButton + xlsxButton = binding.SettingsXlsxCheckbox + xlsButton = binding.SettingsXlsCheckbox + + CoroutineScope(Dispatchers.Main).launch { + settingsViewModel.state.collect{ state -> + xlsxButton.isChecked = state.fileType == ExcelExtension.XLSX + xlsButton.isChecked = state.fileType == ExcelExtension.XLS + } + } saveButton.setOnClickListener { - Toast.makeText(requireContext(), "save",Toast.LENGTH_SHORT).show() + isStoragePermissionGranted() + createExcelFile(settingsViewModel.state.value.fileType,true) } sendButton.setOnClickListener { - // Create an Intent with ACTION_SEND to send an email - val emailIntent = Intent(Intent.ACTION_SEND) + createExcelFile(settingsViewModel.state.value.fileType,false) + } - // Set the type to 'message/rfc822' MIME type, which is the standard MIME type for email messages - emailIntent.type = "message/rfc822" + logoutButton.setOnClickListener { + logout() + } - // Set recipients (to whom you want to send the email) - emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf("13521106@std.stei.itb.ac.id")) + xlsxButton.setOnCheckedChangeListener { _, isChecked -> + if(isChecked){ + settingsViewModel._onEvent(SettingsEvent.SetFileType(ExcelExtension.XLSX)) + xlsButton.isChecked = false + } + } + xlsButton.setOnCheckedChangeListener { _, isChecked -> + if(isChecked){ + settingsViewModel._onEvent(SettingsEvent.SetFileType(ExcelExtension.XLS)) + xlsxButton.isChecked = false + } + } + + } + + private fun logout(){ + CoroutineScope(Dispatchers.IO).launch { + withContext(Dispatchers.Main){ + val sharedPreferences = requireActivity().getSharedPreferences( + "BondoMan", + Context.MODE_PRIVATE + ) + val editor = sharedPreferences.edit() + editor.remove("token") + editor.remove("email") + editor.apply() + startActivity(Intent(activity, LoginActivity::class.java)) + activity?.finish() + } + } + Toast.makeText(requireContext(), "Logged Out",Toast.LENGTH_SHORT).show() + } + + fun createExcelFile(fileType: ExcelExtension, isSaveToLocal: Boolean) { + CoroutineScope(Dispatchers.IO).launch{ + settingsViewModel.transactions.value.collect{ transaction-> + var workbook : Workbook + if(fileType == ExcelExtension.XLS){ + workbook = HSSFWorkbook() + } else { + workbook = XSSFWorkbook() + } - // Set subject of the email - emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Transaction Data") + val filledWorkbook = fillWorkbook(workbook,transaction) - // Set body of the email - emailIntent.putExtra(Intent.EXTRA_TEXT, "This is your data transaction") + if(isSaveToLocal){ + saveWorkBook(filledWorkbook) + } else { + sendMail(filledWorkbook) + } + } + } - // Start the activity with the emailIntent - startActivity(Intent.createChooser(emailIntent, "Send Email")) - // Display a toast indicating the action - Toast.makeText(requireContext(), "Sending email...", Toast.LENGTH_SHORT).show() + } + private fun fillWorkbook(workbook : Workbook, transaction: List<Transaction>) : Workbook{ + val sheets = workbook.createSheet("Sheets 1") + val headerRow = sheets.createRow(0) + val header = arrayOf("Judul", "Nominal","Kategori","Lokasi","Tanggal") + for (i in 0 until header.size) { + val cell = headerRow.createCell(i) + cell.setCellValue(header[i]) + } + for (i in 1 until transaction.size+1){ + val newRow = sheets.createRow(i) + newRow.createCell(0).setCellValue(transaction[i-1].judul) + newRow.createCell(1).setCellValue(transaction[i-1].nominal.toString()) + newRow.createCell(2).setCellValue(transaction[i-1].kategori.toString()) + newRow.createCell(3).setCellValue(transaction[i-1].lokasi.address) + newRow.createCell(4).setCellValue(transaction[i-1].tanggal) } + return workbook + } + private suspend fun saveWorkBook(workbook : Workbook) { + var filename = "BONDOMAN_EXPORT.xls" + // Create a File object like this. + val dir = File("//sdcard//Download//") - logoutButton.setOnClickListener { - Toast.makeText(requireContext(), "logout",Toast.LENGTH_SHORT).show() + when (workbook) { + is XSSFWorkbook -> { + filename = "BONDOMAN_EXPORT.xlsx" + } + else -> { + // Continue + } + } + + val myExternalFile = File(dir,filename) + // Create an object of FileOutputStream for writing data to myFile.txt + var fos: FileOutputStream? = null + try { + // Instantiate the FileOutputStream object and pass myExternalFile in constructor + fos = FileOutputStream(myExternalFile) + // Write to the file + workbook.write(fos) + // Close the stream + fos.close() + workbook.close() + } catch (e: IOException) { + e.printStackTrace() + } + withContext(Dispatchers.Main) { + Toast.makeText(requireContext(), "File downloaded at. $myExternalFile", Toast.LENGTH_SHORT).show() + } + + } + + private suspend fun sendMail(workbook: Workbook) { + try { + val sharedPreferences = requireActivity().getSharedPreferences( + "BondoMan", + Context.MODE_PRIVATE + ) + val email = sharedPreferences.getString("email","") + if(email == ""){ + throw IOException() + } + + // Create a temporary file to save the workbook + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + var fileName = "BONDOMAN_EXPORT_$timeStamp.xls" + if(workbook is XSSFWorkbook){ + fileName = "BONDOMAN_EXPORT_$timeStamp.xlsx" + } + val file = File(requireContext().cacheDir, fileName) + + // Write the workbook to the file + val fileOutputStream = FileOutputStream(file) + workbook.write(fileOutputStream) + fileOutputStream.close() + + val fileUri = FileProvider.getUriForFile(requireContext(), "${requireContext().packageName}.fileprovider", file) + + + // Create an email intent with ACTION_SEND + val emailIntent = Intent(Intent.ACTION_SEND).apply { + type = "message/rfc822" + putExtra(Intent.EXTRA_EMAIL, arrayOf(email)) + putExtra(Intent.EXTRA_SUBJECT, "Export Database Bondoman") + putExtra(Intent.EXTRA_TEXT, "This is your exported database") + putExtra(Intent.EXTRA_STREAM, fileUri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + } + + startActivity(Intent.createChooser(emailIntent, "Send email...")) + } catch (e: IOException) { + e.printStackTrace() + + withContext(Dispatchers.Main) { + Toast.makeText(requireContext(), "Failed to create workbook file", Toast.LENGTH_SHORT).show() + } } + } + private fun isStoragePermissionGranted(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + //Permission is granted + true + } else { + //Permission is revoked + ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) + false + } + } else { + // Permission is automatically granted on sdk<23 upon installation + true + } } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/settings/SettingsState.kt b/app/src/main/java/com/example/bondoman/ui/settings/SettingsState.kt new file mode 100644 index 0000000000000000000000000000000000000000..e54664fc7ecbe4b395f569ffce96ba2fe94d2d93 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/settings/SettingsState.kt @@ -0,0 +1,5 @@ +package com.example.bondoman.ui.settings + +data class SettingsState( + val fileType : ExcelExtension = ExcelExtension.XLSX, +) diff --git a/app/src/main/java/com/example/bondoman/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/example/bondoman/ui/settings/SettingsViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..6c3c7ab5d44e702edc47f32d32143a2412fbd908 --- /dev/null +++ b/app/src/main/java/com/example/bondoman/ui/settings/SettingsViewModel.kt @@ -0,0 +1,55 @@ +package com.example.bondoman.ui.settings + +import android.content.Context +import android.os.Bundle +import androidx.lifecycle.AbstractSavedStateViewModelFactory +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.savedstate.SavedStateRegistryOwner +import com.example.bondoman.room.TransactionDao +import com.example.bondoman.services.TransactionState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update + +class SettingsViewModel(private val dao: TransactionDao, private val context: Context): ViewModel() { + private val _state = MutableStateFlow(SettingsState()) + private val _transactions = MutableStateFlow(dao.observeAll() ) + val transactions = _transactions.asStateFlow() + val state = _state.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), SettingsState()) + + fun _onEvent(event: SettingsEvent){ + when(event){ + is SettingsEvent.SetFileType -> { + _state.update { + it.copy( + fileType = event.fileType + ) + } + } + } + } + + // Define ViewModel factory in a companion object + companion object { + fun provideFactory( + dao: TransactionDao, + context: Context, + owner: SavedStateRegistryOwner, + defaultArgs: Bundle? = null, + ): AbstractSavedStateViewModelFactory = + object : AbstractSavedStateViewModelFactory(owner, defaultArgs) { + @Suppress("UNCHECKED_CAST") + override fun <T : ViewModel> create( + key: String, + modelClass: Class<T>, + handle: SavedStateHandle + ): T { + return SettingsViewModel(dao,context) as T + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_chart.xml b/app/src/main/res/layout/fragment_chart.xml index c4f4655b1165d7a8f6072cdc39202b15658fbeb9..7e58b94e11bcaac6e0c17c6cb28fc65210f7f488 100644 --- a/app/src/main/res/layout/fragment_chart.xml +++ b/app/src/main/res/layout/fragment_chart.xml @@ -1,5 +1,6 @@ <?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" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/ConstraintLayout" android:layout_width="match_parent" @@ -13,4 +14,22 @@ android:id="@+id/chart"> </com.github.mikephil.charting.charts.PieChart> + + <Button + android:id="@+id/sortByValueButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="100dp" + android:text="@string/chart_sort_value_title" + app:layout_constraintBottom_toBottomOf="@+id/chart" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/sortByCountButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/chart_sort_count_title" + app:layout_constraintBottom_toTopOf="@+id/sortByValueButton" + app:layout_constraintStart_toStartOf="parent" /> + </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index f02883c27643546ac18e2533afeaca653675ed4c..e804de014b2e569e1d6e5ad45944d13f86d113e0 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -30,18 +30,55 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/settingsSaveButton" /> + <LinearLayout + android:id="@+id/linearLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:gravity="center" + android:orientation="vertical" + app:layout_constraintTop_toBottomOf="@+id/settingSendButton" + tools:layout_editor_absoluteX="16dp"> + + <TextView + android:id="@+id/textView3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/settings_select_extension_title" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <CheckBox + android:id="@+id/SettingsXlsxCheckbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/settings_xlsx_checkbox_title" /> + + <CheckBox + android:id="@+id/SettingsXlsCheckbox" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/settings_xls_checkbox_title" /> + </LinearLayout> + + </LinearLayout> + <Button android:id="@+id/settingsLogoutButton" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="28dp" + android:layout_marginTop="56dp" android:backgroundTint="@color/red.200" android:text="@string/settings_logout_button_title" android:textColor="@color/white" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/settingSendButton" /> + app:layout_constraintTop_toBottomOf="@+id/linearLayout" /> </androidx.constraintlayout.widget.ConstraintLayout> \ 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 5bd3bf257bbdfe76fe04c2278c9a2d66d7eb41ac..8792f197e94818af8ec0c3dab7175102b22d098a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,4 +14,9 @@ <item>Pembelian</item> <item>Penjualan</item> </string-array> + <string name="chart_sort_value_title">Sort by Value</string> + <string name="chart_sort_count_title">Sort by Count</string> + <string name="settings_select_extension_title">Select file extension</string> + <string name="settings_xlsx_checkbox_title">.xlsx</string> + <string name="settings_xls_checkbox_title">.xls</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000000000000000000000000000000000000..aa4e22f74d1e5c7eb8d0118522ab35264936f7c9 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,3 @@ +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <cache-path name="cache" path="/" /> +</paths> \ No newline at end of file