diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index b1eb6a6d450840fad4d618b2a41a60b21cf7f6c1..e660996cbf03445ae8fb3a3acaa65ae3862be6d1 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -19,7 +19,20 @@ </State> </entry> <entry key="app"> - <State /> + <State> + <runningDeviceTargetSelectedWithDropDown> + <Target> + <type value="RUNNING_DEVICE_TARGET" /> + <deviceKey> + <Key> + <type value="SERIAL_NUMBER" /> + <value value="RR8N2068CCP" /> + </Key> + </deviceKey> + </Target> + </runningDeviceTargetSelectedWithDropDown> + <timeTargetWasSelectedWithDropDown value="2024-03-12T08:14:37.565277300Z" /> + </State> </entry> </value> </component> diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e292238f893c212655679d857003fc0c6011eefe..224d5dc921ff5e8208bf9c1da78b13918ec36535 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -48,6 +48,8 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.3.5") implementation("androidx.room:room-runtime:2.4.0") implementation("androidx.room:room-ktx:2.4.0") + implementation("org.apache.poi:poi:5.2.0") + implementation("org.apache.poi:poi-ooxml:5.2.0") annotationProcessor("androidx.room:room-compiler:2.4.0") ksp("androidx.room:room-compiler:2.4.0") testImplementation("junit:junit:4.13.2") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 73a14b03bad133ae51de885f922e63fab011689a..2cbc1575959f85f008c130f87c695eaea0ec7121 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,7 +23,19 @@ <activity android:name=".ui.hub.HubActivity" android:configChanges="orientation|screenSize"/> <activity android:name=".ui.login.LoginActivity"/> <activity android:name=".ui.transaction.TransactionActivity"/> - </application> + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.provider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/provider_paths" /> + </provider> + + </application> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </manifest> \ No newline at end of file diff --git a/app/src/main/java/com/example/bondoman/ui/hub/HubActivity.kt b/app/src/main/java/com/example/bondoman/ui/hub/HubActivity.kt index 706506eec0248960abfcb382d503805047d5515d..ca22a2217b729828bb9cabbd08aa053ea9b6ca26 100644 --- a/app/src/main/java/com/example/bondoman/ui/hub/HubActivity.kt +++ b/app/src/main/java/com/example/bondoman/ui/hub/HubActivity.kt @@ -4,27 +4,35 @@ import android.content.res.Configuration import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import com.example.bondoman.R import com.example.bondoman.database.AppDatabase +import com.example.bondoman.database.repository.TransactionRepository import com.example.bondoman.databinding.ActivityHubBinding import com.example.bondoman.databinding.ActivityHubLandscapeBinding +import com.example.bondoman.viewmodel.transaction.TransactionViewModel +import com.example.bondoman.viewmodel.transaction.TransactionViewModelFactory import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.navigation.NavigationView class HubActivity : AppCompatActivity() { private lateinit var portrait_binding: ActivityHubBinding private lateinit var landscape_binding: ActivityHubLandscapeBinding + lateinit var transactionViewModel: TransactionViewModel override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState) supportActionBar?.hide() + // Initialize database + val database = AppDatabase.getInstance(this) + val transactionRepo = TransactionRepository(database) + val transactionModelFactory = TransactionViewModelFactory(transactionRepo) + transactionViewModel = ViewModelProvider(this, transactionModelFactory)[TransactionViewModel::class.java] - - // TODO: Make orientation responsive // Initialize navbar and fragments val orientation = resources.configuration.orientation if(orientation == Configuration.ORIENTATION_LANDSCAPE){ diff --git a/app/src/main/java/com/example/bondoman/ui/hub/settings/SettingsFragment.kt b/app/src/main/java/com/example/bondoman/ui/hub/settings/SettingsFragment.kt index f70e59503f0431f1c1c4187c0478134b2ec4bcba..860130cdd04421ea69d9192e58b8b4a25d22e2e5 100644 --- a/app/src/main/java/com/example/bondoman/ui/hub/settings/SettingsFragment.kt +++ b/app/src/main/java/com/example/bondoman/ui/hub/settings/SettingsFragment.kt @@ -1,17 +1,34 @@ package com.example.bondoman.ui.hub.settings +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle +import android.os.Environment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import android.widget.Toast +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.example.bondoman.R +import com.example.bondoman.database.entity.TransactionEntity import com.example.bondoman.databinding.FragmentSettingsBinding -import com.example.bondoman.ui.hub.HubActivity import com.example.bondoman.ui.login.LoginActivity +import com.example.bondoman.viewmodel.transaction.TransactionViewModel +import org.apache.poi.ss.usermodel.BorderStyle +import org.apache.poi.ss.usermodel.FillPatternType +import org.apache.poi.ss.usermodel.IndexedColors +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale class SettingsFragment : Fragment() { private var _binding: FragmentSettingsBinding? = null @@ -40,9 +57,97 @@ class SettingsFragment : Fragment() { } } - //TODO: Implement private fun saveTransaction(view: View){ - println("Transaction saved") + // Request permission + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) + } + + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + return + } + + // Init with viewmodel + val transactionViewModel = ViewModelProvider(requireActivity()).get(TransactionViewModel::class.java) + transactionViewModel.list.observe(viewLifecycleOwner) { transactionList -> + if (transactionList != null){ + + // Initialize excel file + val workbook = XSSFWorkbook() + val workSheet = workbook.createSheet("Transactions") + val headerCellStyle = workbook.createCellStyle() + headerCellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.index) + headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND) + headerCellStyle.setBorderTop(BorderStyle.THIN) + headerCellStyle.setBorderBottom(BorderStyle.THIN) + headerCellStyle.setBorderLeft(BorderStyle.THIN) + headerCellStyle.setBorderRight(BorderStyle.THIN) + + // Initialize headers + val headers = arrayOf("No", "Title", "Category", "Amount", "Location", "Timestamp") + val firstRow = workSheet.createRow(0) + for ((index, header) in headers.withIndex()) { + val cell = firstRow.createCell(index) + cell.setCellValue(header) + cell.cellStyle = headerCellStyle + + workSheet.setColumnWidth(index, 6000) + } + workSheet.setColumnWidth(0, 2000) + + // Insert data + val cellStyle = headerCellStyle.copy() + cellStyle.setFillForegroundColor(IndexedColors.WHITE.index) + cellStyle.wrapText = true + + for ((index, transaction) in transactionList.withIndex()){ + val row = workSheet.createRow(1 + index) + + var cell = row.createCell(0) + cell.setCellValue((1 + index).toString()) + cell.cellStyle = cellStyle + + cell = row.createCell(1) + cell.setCellValue(transaction.title) + cell.cellStyle = cellStyle + + cell = row.createCell(2) + cell.setCellValue(transaction.category) + cell.cellStyle = cellStyle + + cell = row.createCell(3) + cell.setCellValue(transaction.amount.toDouble()) + cell.cellStyle = cellStyle + + cell = row.createCell(4) + cell.setCellValue(transaction.location) + cell.cellStyle = cellStyle + + cell = row.createCell(5) + cell.setCellValue(transaction.timestamp) + cell.cellStyle = cellStyle + } + // Output file + val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + val file = File( + path, + "BondomanTransaction" + SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date()) + ".xlsx" + ) + workbook.write(file.outputStream()) + workbook.close() + + Toast.makeText(requireContext(), "File saved at $path", Toast.LENGTH_SHORT).show() + + // Open file immediately + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(FileProvider.getUriForFile(requireContext(), requireContext().packageName + ".provider", file), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + else{ + Toast.makeText(requireContext(), "Error: Data Unavailable", Toast.LENGTH_SHORT).show() + } + } } //TODO: Implement diff --git a/app/src/main/java/com/example/bondoman/ui/transaction/TransactionActivity.kt b/app/src/main/java/com/example/bondoman/ui/transaction/TransactionActivity.kt index 62b7f6249a7d9e1c5fc19d33d12759938dc0169f..289747c42a05599857e4ed7ab743ba42370e270b 100644 --- a/app/src/main/java/com/example/bondoman/ui/transaction/TransactionActivity.kt +++ b/app/src/main/java/com/example/bondoman/ui/transaction/TransactionActivity.kt @@ -1,10 +1,13 @@ package com.example.bondoman.ui.transaction +import android.content.res.ColorStateList import android.os.Bundle import android.view.View import android.widget.ArrayAdapter +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProvider import com.example.bondoman.R import com.example.bondoman.database.AppDatabase @@ -41,9 +44,8 @@ class TransactionActivity : AppCompatActivity() { // Initialize category dropdown val spinner = binding.categoryInput - val adapter = ArrayAdapter.createFromResource(this, R.array.category_choices, android.R.layout.simple_spinner_item) - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - spinner.adapter = adapter + spinner.setSelection(0, true); + (spinner.selectedView as TextView).setTextColor(ContextCompat.getColor(this, R.color.black)) // Initialize category dropdown val submitButton = binding.submitButton @@ -68,13 +70,15 @@ class TransactionActivity : AppCompatActivity() { category = category, amount = amount.toInt(), // TODO: Location - location = "Dummy Location", + location = binding.locationInput.text.toString(), timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) ) ) + Toast.makeText(this, "Transaction saved successfully", Toast.LENGTH_SHORT).show() +// TODO: Delete, this is for testing purposes +// transactionViewModel.deleteAll() + onBackPressed() } - // TODO: Delete, this is for testing purposes - transactionViewModel.deleteAll() } } diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 7a2c5d3bf951d3b3c1e8ad56a38bc936419a99a9..9573a5991a419141d0ed95da041215b9473727dd 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -14,7 +14,7 @@ android:layout_marginBottom="24dp" android:textSize="14pt" android:fontFamily="sans-serif-medium" - android:textColor="@color/black" + android:textColor="?android:textColorPrimary" app:layout_constraintBottom_toTopOf="@+id/login_cluster" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -35,7 +35,7 @@ android:layout_height="wrap_content" android:text="@string/login_label_email" android:textSize="10pt" - android:textColor="@color/black"/> + android:textColor="?android:textColorPrimary"/> <EditText android:paddingHorizontal="10sp" @@ -44,7 +44,8 @@ android:layout_height="16pt" android:ems="10" android:inputType="text" - android:background="@color/gray"/> + android:background="@color/gray" + android:textColor="@color/black"/> <TextView @@ -53,7 +54,7 @@ android:layout_height="wrap_content" android:text="@string/login_label_password" android:textSize="10pt" - android:textColor="@color/black" + android:textColor="?android:textColorPrimary" android:layout_marginTop="5pt"/> <EditText @@ -63,7 +64,8 @@ android:layout_height="16pt" android:ems="10" android:inputType="text" - android:background="@color/gray"/> + android:background="@color/gray" + android:textColor="@color/black"/> <Button android:id="@+id/login_button" diff --git a/app/src/main/res/layout/activity_transaction.xml b/app/src/main/res/layout/activity_transaction.xml index 2120d54ce09f67d2f53f8d31143c8a74930d12d3..3cdd6fc9976c6065ddb20e6171e61284eed02059 100644 --- a/app/src/main/res/layout/activity_transaction.xml +++ b/app/src/main/res/layout/activity_transaction.xml @@ -29,7 +29,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/transaction_label_title" - android:textColor="@color/black" + android:textColor="?android:textColorPrimary" android:textSize="10pt" /> <EditText @@ -39,7 +39,8 @@ android:layout_height="16pt" android:background="@color/gray" android:ems="10" - android:inputType="text" /> + android:inputType="text" + android:textColor="@color/black"/> <TextView android:id="@+id/amount_label" @@ -47,7 +48,7 @@ android:layout_height="wrap_content" android:layout_marginTop="5pt" android:text="@string/transaction_label_amount" - android:textColor="@color/black" + android:textColor="?android:textColorPrimary" android:textSize="10pt" /> <EditText @@ -57,21 +58,23 @@ android:layout_height="16pt" android:background="@color/gray" android:ems="10" - android:inputType="numberDecimal" /> + android:inputType="numberDecimal" + android:textColor="@color/black"/> <TextView android:id="@+id/category_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/transaction_label_category" - android:textColor="@color/black" + android:textColor="?android:textColorPrimary" android:textSize="10pt" /> <Spinner android:id="@+id/category_input" android:layout_width="match_parent" android:layout_height="16pt" - android:background="@color/gray"/> + android:background="@color/gray" + android:entries="@array/category_choices"/> <TextView android:id="@+id/location_label" @@ -79,7 +82,7 @@ android:layout_height="wrap_content" android:layout_marginTop="5pt" android:text="@string/transaction_label_location" - android:textColor="@color/black" + android:textColor="?android:textColorPrimary" android:textSize="10pt" /> <EditText @@ -89,7 +92,8 @@ android:layout_height="16pt" android:background="@color/gray" android:ems="10" - android:inputType="text" /> + android:inputType="text" + android:textColor="@color/black"/> <Button android:id="@+id/submit_button" diff --git a/app/src/main/res/layout/header_layout.xml b/app/src/main/res/layout/header_layout.xml index bbc09cf9610163c9dd313fb875dbf6e5047d14ab..b5955ac037ed6fc135ddd7133f272b24d547f63c 100644 --- a/app/src/main/res/layout/header_layout.xml +++ b/app/src/main/res/layout/header_layout.xml @@ -21,11 +21,12 @@ <ImageButton android:id="@+id/nav_back_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="35dp" + android:layout_height="25dp" android:layout_gravity="center_vertical" android:background="@android:color/transparent" android:src="@drawable/ic_back" + android:layout_marginLeft="10dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index baa73874f455bea028e195e447e3aa7146e2cb40..727e75dd47bbd706bf839abfd194cdc407136db7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,6 +7,7 @@ <color name="teal_700">#FF018786</color> <color name="black">#FF000000</color> <color name="gray">#FFd9d9d9</color> + <color name="gray_500">#FF797979</color> <color name="white">#FFFFFFFF</color> <color name="red">#FFCE0000</color> </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..4495c28c86d1340dba06826bf7a03519fe25aa88 --- /dev/null +++ b/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths> + <external-path name="external_files" path="."/> +</paths> \ No newline at end of file