diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index fb7f4a8a..b589d56e 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index fbd67c2d..00000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 61413d36..cb865f69 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,17 +4,15 @@
-
-
-
-
+
+
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 00000000..2b8a50fc
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 96869c6e..dc82b60f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
@@ -145,10 +144,17 @@
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index b5ff13fc..5166153b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -28,13 +28,12 @@ String getManageYourRentersBaseUrl() {
}
android {
- compileSdkVersion 33
- buildToolsVersion "33.0.0"
+ compileSdk 34
defaultConfig {
applicationId "com.rohitthebest.manageyourrenters"
- minSdkVersion 23
- targetSdkVersion 33
+ minSdk 23
+ targetSdk 34
versionCode 5
versionName "5.3.1"
@@ -177,4 +176,6 @@ dependencies {
//https://developer.android.com/studio/write/java8-support#groovy
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
// don't change the version until gradle plugin is updated to 7.4.0-alpha10
+
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3314abf1..98b7b8d3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,11 +9,11 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
(DiffUtilCallback()) {
+
+ private var mListener: OnClickListener? = null
+
+ inner class BudgetViewHolder(val binding: AdapterBudgetBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ init {
+
+ binding.root.setOnClickListener {
+
+ if (isPositionAndMlistenerValid()) {
+ mListener!!.onItemClick(getItem(absoluteAdapterPosition))
+ }
+ }
+
+ binding.baMenuBtn.setOnClickListener {
+ if (isPositionAndMlistenerValid()) {
+ mListener!!.onMenuBtnClick(
+ getItem(absoluteAdapterPosition),
+ binding.baMenuBtn,
+ absoluteAdapterPosition
+ )
+ }
+ }
+
+ binding.baDetailsBtn.setOnClickListener {
+ if (isPositionAndMlistenerValid()) {
+
+ mListener!!.onDetailsButtonClicked(getItem(absoluteAdapterPosition))
+ }
+ }
+ }
+
+ private fun isPositionAndMlistenerValid(): Boolean {
+ return mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION
+ }
+
+ fun setData(budget: Budget?) {
+
+ budget?.let { myBudget ->
+
+ binding.apply {
+
+ baCategoryNameTV.text = myBudget.categoryName
+
+ if (myBudget.categoryImageUrl.isValid()) {
+
+ Functions.setImageToImageViewUsingGlide(
+ binding.root.context,
+ baCategoryImageIV,
+ myBudget.categoryImageUrl,
+ {},
+ {}
+ )
+ } else {
+
+ Glide.with(binding.root)
+ .load(R.drawable.expense_shortcut_icon)
+ .transition(DrawableTransitionOptions.withCrossFade())
+ .into(baCategoryImageIV)
+ }
+
+ baSpentVsLimitTV.text = binding.root.context.getString(
+ R.string.spentVsLimit,
+ myBudget.currentExpenseAmount.format(2),
+ myBudget.budgetLimit.format(2)
+ )
+
+ val numberOfDaysLeftInMonth =
+ WorkingWithDateAndTime.getNumberOfDaysLeftInAnyMonth(
+ myBudget.month, myBudget.year
+ )
+
+ Log.d(TAG, "setData: numberOfDaysLeftInMonth: $numberOfDaysLeftInMonth")
+
+ var perDayExpense = if (numberOfDaysLeftInMonth != 0) {
+ (myBudget.budgetLimit - myBudget.currentExpenseAmount) / numberOfDaysLeftInMonth
+ } else {
+ 0.0
+ }
+
+ if (perDayExpense < 0) perDayExpense = 0.0
+
+ baPerDayExpenseTV.text = binding.root.context.getString(
+ R.string.budgetPerDay, perDayExpense.format(2),
+ numberOfDaysLeftInMonth.toString()
+ )
+
+ val progressInPercent =
+ ((myBudget.currentExpenseAmount / myBudget.budgetLimit) * 100).toInt()
+
+ percentTV.text = if (progressInPercent > 100.0) {
+ binding.root.context.getString(R.string._100)
+ } else {
+ "$progressInPercent%"
+ }
+
+ changeProgressColorsByProgressPercent(progressInPercent)
+
+ baProgressBar.max = myBudget.budgetLimit.toInt()
+
+ if (myBudget.currentExpenseAmount > myBudget.budgetLimit) {
+ baProgressBar.progress = myBudget.budgetLimit.toInt()
+ } else {
+ baProgressBar.progress = myBudget.currentExpenseAmount.toInt()
+ }
+ }
+ }
+ }
+
+ private fun changeProgressColorsByProgressPercent(progressInPercent: Int) {
+
+ when {
+
+ (progressInPercent in 0..35) -> {
+ val colorGreen = ContextCompat.getColor(
+ binding.root.context,
+ R.color.color_green
+ )
+ binding.baProgressBar.progressTintList = ColorStateList.valueOf(
+ colorGreen
+ )
+ binding.percentMCV.strokeColor = colorGreen
+
+ }
+
+ (progressInPercent in 36..68) -> {
+ val colorYellow = ContextCompat.getColor(
+ binding.root.context,
+ R.color.color_yellow
+ )
+ binding.baProgressBar.progressTintList = ColorStateList.valueOf(
+ colorYellow
+ )
+ binding.percentMCV.strokeColor = colorYellow
+
+ }
+
+ else -> {
+ val colorRed = ContextCompat.getColor(
+ binding.root.context,
+ R.color.color_Red
+ )
+ binding.baProgressBar.progressTintList = ColorStateList.valueOf(
+ colorRed
+ )
+
+ binding.percentMCV.strokeColor = colorRed
+ }
+ }
+ }
+ }
+
+ companion object {
+
+ class DiffUtilCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: Budget, newItem: Budget): Boolean =
+ oldItem.key == newItem.key
+
+ override fun areContentsTheSame(oldItem: Budget, newItem: Budget): Boolean =
+ oldItem == newItem
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BudgetViewHolder {
+
+ val binding =
+ AdapterBudgetBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+
+ return BudgetViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: BudgetViewHolder, position: Int) {
+
+ holder.setData(getItem(position))
+ }
+
+ interface OnClickListener {
+
+ fun onItemClick(budget: Budget)
+ fun onMenuBtnClick(budget: Budget, view: View, position: Int)
+ fun onDetailsButtonClicked(budget: Budget)
+ }
+
+ fun setOnClickListener(listener: OnClickListener) {
+ mListener = listener
+ }
+}
+
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/IncomeRVAdapter.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/IncomeRVAdapter.kt
new file mode 100644
index 00000000..a8b75283
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/IncomeRVAdapter.kt
@@ -0,0 +1,113 @@
+package com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.databinding.AdapterIncomeListItemBinding
+import com.rohitthebest.manageyourrenters.utils.format
+
+class IncomeRVAdapter(
+ val linkedPaymentMethodsMap: Map = emptyMap(),
+) : ListAdapter(DiffUtilCallback()) {
+
+ private var mListener: OnClickListener? = null
+
+ inner class IncomeViewHolder(val binding: AdapterIncomeListItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ init {
+
+ binding.root.setOnClickListener {
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onItemClick(getItem(absoluteAdapterPosition))
+ }
+ }
+
+ binding.incomeMenuBtn.setOnClickListener {
+
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onIncomeItemMenuBtnClicked(
+ getItem(absoluteAdapterPosition),
+ binding.incomeMenuBtn
+ )
+ }
+ }
+
+ binding.incomeSyncBtn.setOnClickListener {
+
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onSyncBtnCLicked(
+ getItem(absoluteAdapterPosition),
+ absoluteAdapterPosition
+ )
+ }
+ }
+
+ }
+
+ fun setData(income: Income?) {
+
+ income?.let { myIncome ->
+
+ binding.apply {
+
+ incomeValueTV.text = myIncome.income.format(2)
+ incomeSourceValueTV.text = myIncome.source
+ incomeSyncBtn.isVisible = !myIncome.isSynced
+
+ if (linkedPaymentMethodsMap.isNotEmpty()) {
+ lincomeLinkedPaymentMethodsTV.text = myIncome.getPaymentMethodString(
+ paymentMethodsMap = linkedPaymentMethodsMap
+ )
+ }
+
+ }
+ }
+ }
+ }
+
+ companion object {
+
+ class DiffUtilCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: Income, newItem: Income): Boolean =
+ oldItem.key == newItem.key
+
+ override fun areContentsTheSame(oldItem: Income, newItem: Income): Boolean =
+ oldItem == newItem
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IncomeViewHolder {
+
+ val binding =
+ AdapterIncomeListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+
+ return IncomeViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: IncomeViewHolder, position: Int) {
+
+ holder.setData(getItem(position))
+ }
+
+ interface OnClickListener {
+
+ fun onItemClick(income: Income)
+ fun onIncomeItemMenuBtnClicked(income: Income, view: View)
+ fun onSyncBtnCLicked(income: Income, position: Int)
+ }
+
+ fun setOnClickListener(listener: OnClickListener) {
+ mListener = listener
+ }
+}
+
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/SelectMonthAndYearAdapter.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/SelectMonthAndYearAdapter.kt
new file mode 100644
index 00000000..32d699c3
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/SelectMonthAndYearAdapter.kt
@@ -0,0 +1,98 @@
+package com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.databinding.AdapterSelectMonthAndYearBinding
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+
+class SelectMonthAndYearAdapter :
+ ListAdapter(DiffUtilCallback()) {
+
+ private var mListener: OnClickListener? = null
+
+ inner class SelectMonthAndYearViewHolder(val binding: AdapterSelectMonthAndYearBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ private var monthList: List = emptyList()
+
+ init {
+
+ monthList = binding.root.context.resources.getStringArray(R.array.months).toList()
+
+ binding.monthYearRB.setOnClickListener {
+
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onMonthAndYearClicked(getItem(absoluteAdapterPosition))
+ }
+ }
+ }
+
+ fun setData(monthAndYearString: String?) {
+
+ monthAndYearString?.let { monthYearString ->
+
+ binding.apply {
+
+ val monthAndYear =
+ WorkingWithDateAndTime.extractMonthAndYearFromMonthAndYearString(
+ monthYearString
+ )
+
+ val month = monthList[monthAndYear.first]
+
+ monthYearRB.text = binding.root.context.getString(
+ R.string.month_and_year,
+ month,
+ monthAndYear.second.toString()
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+
+ class DiffUtilCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: String, newItem: String): Boolean =
+ oldItem == newItem
+
+ override fun areContentsTheSame(oldItem: String, newItem: String): Boolean =
+ oldItem == newItem
+ }
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): SelectMonthAndYearViewHolder {
+
+ val binding = AdapterSelectMonthAndYearBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+
+ return SelectMonthAndYearViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: SelectMonthAndYearViewHolder, position: Int) {
+
+ holder.setData(getItem(position))
+ }
+
+ interface OnClickListener {
+
+ fun onMonthAndYearClicked(monthAndYear: String)
+ }
+
+ fun setOnClickListener(listener: OnClickListener) {
+ mListener = listener
+ }
+}
+
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/SetBudgetExpenseCategoryAdapter.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/SetBudgetExpenseCategoryAdapter.kt
new file mode 100644
index 00000000..ebe62718
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/adapters/trackMoneyAdapters/expenseAdapters/budgetAndIncome/SetBudgetExpenseCategoryAdapter.kt
@@ -0,0 +1,221 @@
+package com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome
+
+import android.content.res.ColorStateList
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.databinding.AdapterSetBudgetExpenseCategoryBinding
+import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.changeTextColor
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.isValid
+import com.rohitthebest.manageyourrenters.utils.show
+
+class SetBudgetExpenseCategoryAdapter :
+ ListAdapter(DiffUtilCallback()) {
+
+ private var mListener: OnClickListener? = null
+
+ inner class SetBudgetViewHolder(val binding: AdapterSetBudgetExpenseCategoryBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ init {
+
+ binding.budgetAddLimitBtn.setOnClickListener {
+
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onAddBudgetClicked(
+ getItem(absoluteAdapterPosition),
+ absoluteAdapterPosition
+ )
+ }
+ }
+
+ binding.budgetMenuBtn.setOnClickListener {
+
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onBudgetMenuBtnClicked(
+ getItem(absoluteAdapterPosition),
+ binding.budgetMenuBtn,
+ absoluteAdapterPosition
+ )
+ }
+ }
+
+ binding.budgetSyncBtn.setOnClickListener {
+
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+
+ mListener!!.onBudgetSyncBtnClicked(
+ getItem(absoluteAdapterPosition),
+ absoluteAdapterPosition
+ )
+ }
+ }
+
+ binding.root.setOnClickListener {
+ if (mListener != null && absoluteAdapterPosition != RecyclerView.NO_POSITION) {
+ mListener!!.onItemClicked(
+ getItem(absoluteAdapterPosition).expenseCategoryKey,
+ getItem(absoluteAdapterPosition).budgetLimit != 0.0
+ )
+ }
+ }
+ }
+
+ fun setData(budget: Budget?) {
+
+ budget?.let { myBudget ->
+
+ binding.apply {
+
+ budgetCategoryNameTV.text = myBudget.categoryName
+ budgetCurrent.text = myBudget.currentExpenseAmount.format(2)
+
+ if (myBudget.categoryImageUrl.isValid()) {
+
+ Functions.setImageToImageViewUsingGlide(
+ binding.root.context,
+ categoryImage,
+ myBudget.categoryImageUrl,
+ {},
+ {}
+ )
+ } else {
+
+ Glide.with(binding.root)
+ .load(R.drawable.expense_shortcut_icon)
+ .transition(DrawableTransitionOptions.withCrossFade())
+ .into(categoryImage)
+ }
+
+ if (myBudget.budgetLimit == 0.0) {
+
+ // no budget is set
+ budgetAddLimitBtn.show()
+ budgetLimitTV.hide()
+ budgetMenuBtn.hide()
+ budgetSyncBtn.hide()
+ } else {
+ budgetAddLimitBtn.hide()
+ budgetLimitTV.show()
+ budgetMenuBtn.show()
+
+ budgetSyncBtn.isVisible = !myBudget.isSynced
+
+ budgetLimitTV.text = myBudget.budgetLimit.format(2)
+
+ budgetProgressBar.max = myBudget.budgetLimit.toInt()
+
+ }
+
+ val progressInPercent =
+ ((myBudget.currentExpenseAmount / myBudget.budgetLimit) * 100).toInt()
+
+ when {
+
+ (progressInPercent in 0..35) -> {
+
+ budgetCurrent.changeTextColor(binding.root.context, R.color.color_green)
+ budgetProgressBar.progressTintList = ColorStateList.valueOf(
+ ContextCompat.getColor(
+ binding.root.context,
+ R.color.color_green
+ )
+ )
+ }
+
+ (progressInPercent in 36..68) -> {
+
+ budgetCurrent.changeTextColor(
+ binding.root.context,
+ R.color.color_yellow
+ )
+ budgetProgressBar.progressTintList = ColorStateList.valueOf(
+ ContextCompat.getColor(
+ binding.root.context,
+ R.color.color_yellow
+ )
+ )
+ }
+
+ progressInPercent > 68 -> {
+
+ budgetCurrent.changeTextColor(
+ binding.root.context,
+ R.color.color_Red
+ )
+ budgetProgressBar.progressTintList = ColorStateList.valueOf(
+ ContextCompat.getColor(
+ binding.root.context,
+ R.color.color_Red
+ )
+ )
+ }
+ }
+
+ if (myBudget.currentExpenseAmount > myBudget.budgetLimit) {
+ budgetProgressBar.progress = myBudget.budgetLimit.toInt()
+ } else {
+ budgetProgressBar.progress = myBudget.currentExpenseAmount.toInt()
+ }
+
+ }
+ }
+ }
+ }
+
+ companion object {
+
+ class DiffUtilCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: Budget, newItem: Budget): Boolean =
+ oldItem.key == newItem.key
+
+ override fun areContentsTheSame(oldItem: Budget, newItem: Budget): Boolean =
+ oldItem == newItem
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetBudgetViewHolder {
+
+ val binding = AdapterSetBudgetExpenseCategoryBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+
+ return SetBudgetViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: SetBudgetViewHolder, position: Int) {
+
+ holder.setData(getItem(position))
+ }
+
+ interface OnClickListener {
+
+ fun onItemClicked(expenseCategoryKey: String, isBudgetLimitAdded: Boolean)
+ fun onAddBudgetClicked(budget: Budget, position: Int)
+ fun onBudgetMenuBtnClicked(budget: Budget, view: View, position: Int)
+
+ fun onBudgetSyncBtnClicked(budget: Budget, position: Int)
+ }
+
+ fun setOnClickListener(listener: OnClickListener) {
+ mListener = listener
+ }
+}
+
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/data/ExpenseCategoryAndTheirTotalExpenseAmounts.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/data/ExpenseCategoryAndTheirTotalExpenseAmounts.kt
new file mode 100644
index 00000000..ddcb70c4
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/data/ExpenseCategoryAndTheirTotalExpenseAmounts.kt
@@ -0,0 +1,7 @@
+package com.rohitthebest.manageyourrenters.data
+
+data class ExpenseCategoryAndTheirTotalExpenseAmounts(
+ var expenseCategoryKey: String,
+ var categoryName: String,
+ var totalAmount: Double
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/data/MonthAndTotalSum.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/data/MonthAndTotalSum.kt
new file mode 100644
index 00000000..c2f86d9a
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/data/MonthAndTotalSum.kt
@@ -0,0 +1,6 @@
+package com.rohitthebest.manageyourrenters.data
+
+data class MonthAndTotalSum(
+ var month: Int,
+ var total: Double
+)
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/data/ShowExpenseBottomSheetTagsEnum.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/data/ShowExpenseBottomSheetTagsEnum.kt
index 71fba8cd..3130aebb 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/data/ShowExpenseBottomSheetTagsEnum.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/data/ShowExpenseBottomSheetTagsEnum.kt
@@ -3,5 +3,6 @@ package com.rohitthebest.manageyourrenters.data
enum class ShowExpenseBottomSheetTagsEnum {
GRAPH_FRAGMENT,
- PAYMENT_METHOD_FRAGMENT
+ PAYMENT_METHOD_FRAGMENT,
+ BUDGET_AND_INCOME_FRAGMENT
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/data/filter/BudgetAndIncomeExpenseFilter.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/data/filter/BudgetAndIncomeExpenseFilter.kt
new file mode 100644
index 00000000..43a3807e
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/data/filter/BudgetAndIncomeExpenseFilter.kt
@@ -0,0 +1,14 @@
+package com.rohitthebest.manageyourrenters.data.filter
+
+import java.io.Serializable
+
+data class BudgetAndIncomeExpenseFilter(
+ var categoryKeys: List,
+ var paymentMethods: List = emptyList() // if empty then show all the expenses
+) : Serializable {
+
+ constructor() : this(
+ emptyList(),
+ emptyList()
+ )
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/BudgetDao.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/BudgetDao.kt
new file mode 100644
index 00000000..a7f52720
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/BudgetDao.kt
@@ -0,0 +1,76 @@
+package com.rohitthebest.manageyourrenters.database.dao
+
+import androidx.room.*
+import com.rohitthebest.manageyourrenters.data.MonthAndTotalSum
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface BudgetDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertBudget(budget: Budget)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertAllBudget(budgets: List)
+
+ @Update(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun updateBudget(budget: Budget)
+
+ @Delete
+ suspend fun deleteBudget(budget: Budget)
+
+ @Query("DELETE FROM budget_table")
+ suspend fun deleteAllBudgets()
+
+ @Query("DELETE FROM budget_table WHERE expenseCategoryKey = :expenseCategoryKey")
+ suspend fun deleteBudgetsByExpenseCategoryKey(expenseCategoryKey: String)
+
+ @Query("DELETE FROM budget_table WHERE month = :month AND year = :year")
+ suspend fun deleteBudgetsByMonthAndYear(month: Int, year: Int)
+
+ @Query("DELETE FROM budget_table WHERE isSynced = :isSynced")
+ suspend fun deleteByIsSyncedValue(isSynced: Boolean)
+
+ @Query("SELECT * FROM budget_table")
+ fun getAllBudgets(): Flow>
+
+ @Query("SELECT * FROM budget_table WHERE `key` IN (:keyList)")
+ fun getAllBudgetsByKey(keyList: List): Flow>
+
+ @Query("SELECT * FROM budget_table where month = :month and year = :year order by created DESC")
+ fun getAllBudgetsByMonthAndYear(month: Int, year: Int): Flow>
+
+ @Query("SELECT * FROM budget_table where monthYearString = :monthYearString order by created DESC")
+ fun getAllBudgetsByMonthAndYearString(monthYearString: String): Flow>
+
+ @Query("SELECT year FROM budget_table order by year ASC LIMIT 1")
+ fun getTheOldestSavedBudgetYear(): Flow
+
+ @Query("SELECT * FROM budget_table where `key` = :budgetKey")
+ fun getBudgetBuyBudgetKey(budgetKey: String): Flow
+
+ @Query("SELECT expenseCategoryKey FROM budget_table where month = :month and year = :year")
+ fun getExpenseCategoryKeysOfAllBudgetsByMonthAndYear(month: Int, year: Int): Flow>
+
+ @Query("SELECT SUM(budgetLimit) FROM budget_table where month = :month and year = :year")
+ fun getTotalBudgetByMonthAndYear(month: Int, year: Int): Flow
+
+ @Query("SELECT month, SUM(budgetLimit) AS total FROM budget_table where year = :year GROUP BY month, year")
+ fun getAllTotalBudgetByYear(year: Int): Flow>
+
+ @Query("UPDATE budget_table SET isSynced = 0 WHERE `key` = :key")
+ suspend fun updateIsSyncedValueToFalse(key: String)
+
+ @Query("SELECT `key` FROM budget_table WHERE expenseCategoryKey = :expenseCategoryKey")
+ suspend fun getKeysByExpenseCategoryKey(expenseCategoryKey: String): List
+
+ @Query("SELECT `key` FROM budget_table WHERE month = :month AND year = :year")
+ suspend fun getKeysByMonthAndYear(month: Int, year: Int): List
+
+ @Query("SELECT DISTINCT monthYearString FROM budget_table ORDER BY year DESC, month DESC")
+ fun getAllBudgetMonthAndYearForWhichBudgetIsAdded(): Flow>
+
+ @Query("SELECT `key` FROM budget_table WHERE monthYearString = :monthYearString LIMIT 2")
+ fun isAnyBudgetAddedForThisMonthAndYear(monthYearString: String): Flow>
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseCategoryDAO.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseCategoryDAO.kt
index 34e58ad4..4596af6c 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseCategoryDAO.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseCategoryDAO.kt
@@ -28,9 +28,15 @@ interface ExpenseCategoryDAO {
@Query("SELECT * FROM expense_category_table ORDER BY modified DESC")
fun getAllExpenseCategories(): Flow>
+ @Query("SELECT * FROM expense_category_table limit :limit")
+ fun getAllExpenseCategoriesByLimit(limit: Int): Flow>
+
@Query("SELECT * FROM expense_category_table WHERE `key` = :key")
fun getExpenseCategoryByKey(key: String): Flow
+ @Query("SELECT * FROM expense_category_table WHERE `key` in (:expenseCategoryKeys)")
+ fun getExpenseCategoriesByKey(expenseCategoryKeys: List): Flow>
+
@Query("UPDATE expense_category_table SET isSynced = 0 WHERE `key` = :key")
suspend fun updateIsSyncedValueToFalse(key: String)
}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseDAO.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseDAO.kt
index d3754afe..8ff5e7ba 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseDAO.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/ExpenseDAO.kt
@@ -1,6 +1,7 @@
package com.rohitthebest.manageyourrenters.database.dao
import androidx.room.*
+import com.rohitthebest.manageyourrenters.data.ExpenseCategoryAndTheirTotalExpenseAmounts
import com.rohitthebest.manageyourrenters.database.model.Expense
import kotlinx.coroutines.flow.Flow
@@ -60,6 +61,26 @@ interface ExpenseDAO {
date2: Long
): Flow
+ @Query("SELECT SUM(amount) FROM expense_table WHERE categoryKey IN (:expenseCategoryKeys)")
+ fun getTotalExpenseByCategoryKeys(
+ expenseCategoryKeys: List
+ ): Flow
+
+
+ @Query("SELECT SUM(amount) FROM expense_table WHERE categoryKey IN (:expenseCategoryKeys) AND created BETWEEN :date1 AND :date2")
+ fun getTotalExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ): Flow
+
+ @Query("SELECT * FROM expense_table WHERE categoryKey IN (:expenseCategoryKeys) AND created BETWEEN :date1 AND :date2 order by created DESC")
+ fun getExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ): Flow>
+
@Query("SELECT * FROM expense_table WHERE `key` = :expenseKey")
fun getExpenseByKey(expenseKey: String): Flow
@@ -89,4 +110,98 @@ interface ExpenseDAO {
@Query("UPDATE expense_table SET isSynced = 0 WHERE `key` = :key")
suspend fun updateIsSyncedValueToFalse(key: String)
+
+ @Query(
+ "SELECT expense_category_table.`key` as expenseCategoryKey, " +
+ "expense_category_table.categoryName AS categoryName, " +
+ "SUM(expense_table.amount) AS totalAmount " +
+ "FROM expense_table " +
+ "INNER JOIN expense_category_table " +
+ "ON expense_table.categoryKey = expense_category_table.`key` " +
+ "GROUP BY expense_category_table.`key`"
+ )
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeys(): Flow>
+
+ @Query(
+ "SELECT expense_category_table.`key` as expenseCategoryKey, " +
+ "expense_category_table.categoryName AS categoryName, " +
+ "SUM(expense_table.amount) AS totalAmount " +
+ "FROM expense_table " +
+ "INNER JOIN expense_category_table " +
+ "ON expense_table.categoryKey = expense_category_table.`key` " +
+ "WHERE expense_table.`key` in (:expenseKeys) " +
+ "GROUP BY expense_category_table.`key`"
+ )
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByListOfExpenseKeys(
+ expenseKeys: List
+ ): Flow>
+
+
+ @Query(
+ "SELECT expense_category_table.`key` AS expenseCategoryKey, " +
+ "expense_category_table.categoryName AS categoryName, " +
+ "SUM(expense_table.amount) AS totalAmount " +
+ "FROM expense_table " +
+ "INNER JOIN expense_category_table " +
+ "ON expense_table.categoryKey = expense_category_table.`key` " +
+ "WHERE expense_table.created BETWEEN :date1 AND :date2 " +
+ "GROUP BY expense_category_table.`key`"
+ )
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRange(
+ date1: Long,
+ date2: Long
+ ): Flow>
+
+ @Query(
+ "SELECT expense_category_table.`key` AS expenseCategoryKey, " +
+ "expense_category_table.categoryName AS categoryName, " +
+ "SUM(expense_table.amount) AS totalAmount " +
+ "FROM expense_table " +
+ "INNER JOIN expense_category_table " +
+ "ON expense_table.categoryKey = expense_category_table.`key` " +
+ "WHERE expense_table.created BETWEEN :date1 AND :date2 " +
+ "AND expense_table.`key` IN (:expenseKeys) " +
+ "GROUP BY expense_category_table.`key`"
+ )
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRangeAndByListOfExpenseKeys(
+ date1: Long,
+ date2: Long,
+ expenseKeys: List
+ ): Flow>
+
+
+ @Query(
+ "SELECT expense_category_table.`key` AS expenseCategoryKey, " +
+ "expense_category_table.categoryName AS categoryName, " +
+ "SUM(expense_table.amount) AS totalAmount " +
+ "FROM expense_table " +
+ "INNER JOIN expense_category_table " +
+ "ON expense_table.categoryKey = expense_category_table.`key` " +
+ "WHERE expense_table.categoryKey IN (:selectedExpenseCategories) " +
+ "GROUP BY expense_category_table.`key`"
+ )
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategories(
+ selectedExpenseCategories: List
+ ): Flow>
+
+ @Query(
+ "SELECT expense_category_table.`key` AS expenseCategoryKey, " +
+ "expense_category_table.categoryName AS categoryName, " +
+ "SUM(expense_table.amount) AS totalAmount " +
+ "FROM expense_table " +
+ "INNER JOIN expense_category_table " +
+ "ON expense_table.categoryKey = expense_category_table.`key` " +
+ "WHERE expense_table.categoryKey IN (:selectedExpenseCategories) " +
+ "AND expense_table.created BETWEEN :date1 AND :date2 " +
+ "GROUP BY expense_category_table.`key`"
+ )
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategoriesByDateRange(
+ selectedExpenseCategories: List,
+ date1: Long,
+ date2: Long
+ ): Flow>
+
+
+ @Query("SELECT EXISTS(select `key` from expense_table limit 1)")
+ fun isAnyExpenseAdded(): Flow
}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/IncomeDao.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/IncomeDao.kt
new file mode 100644
index 00000000..7723c579
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/IncomeDao.kt
@@ -0,0 +1,50 @@
+package com.rohitthebest.manageyourrenters.database.dao
+
+import androidx.room.*
+import com.rohitthebest.manageyourrenters.data.MonthAndTotalSum
+import com.rohitthebest.manageyourrenters.database.model.Income
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface IncomeDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertIncome(income: Income)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertAllIncome(incomes: List)
+
+ @Update
+ suspend fun updateIncome(income: Income)
+
+ @Delete
+ suspend fun deleteIncome(income: Income)
+
+ @Query("DELETE FROM income_table WHERE isSynced = :isSynced")
+ suspend fun deleteByIsSyncedValue(isSynced: Boolean)
+
+ @Query("DELETE FROM income_table")
+ suspend fun deleteAllIncomes()
+
+ @Query("SELECT * FROM income_table")
+ fun getAllIncomes(): Flow>
+
+ @Query("SELECT * FROM income_table where month = :month AND year = :year")
+ fun getAllIncomesByMonthAndYear(month: Int, year: Int): Flow>
+
+ @Query("SELECT * FROM income_table where `key` = :incomeKey")
+ fun getIncomeByKey(incomeKey: String): Flow
+
+ @Query("SELECT SUM(income) FROM income_table where month = :month AND year = :year")
+ fun getTotalIncomeAddedByMonthAndYear(month: Int, year: Int): Flow
+
+ @Query("SELECT month, SUM(income) AS total FROM income_table where year = :year GROUP BY month, year")
+ fun getAllTotalIncomeByYear(year: Int): Flow>
+
+
+ @Query("SELECT DISTINCT source FROM income_table")
+ fun getAllIncomeSources(): Flow>
+
+ @Query("UPDATE income_table SET isSynced = 0 WHERE `key` = :key")
+ suspend fun updateIsSyncedValueToFalse(key: String)
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/RenterDao.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/RenterDao.kt
index 21abb6a3..4c378a57 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/RenterDao.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/dao/RenterDao.kt
@@ -55,6 +55,10 @@ interface RenterDao {
endDate: Long
): Flow>>
+ @MapInfo(keyColumn = "renterName", valueColumn = "dues")
+ @Query("SELECT renter_table.name AS renterName, renter_table.dueOrAdvanceAmount AS dues FROM renter_table WHERE dues < 0")
+ fun getRentersWithTheirDues(): Flow>
+
@Query("SELECT DISTINCT address FROM renter_table")
fun getAllDistinctAddress(): Flow>
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/databases/BudgetAndIncomeDatabase.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/databases/BudgetAndIncomeDatabase.kt
new file mode 100644
index 00000000..2581bf94
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/databases/BudgetAndIncomeDatabase.kt
@@ -0,0 +1,22 @@
+package com.rohitthebest.manageyourrenters.database.databases
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import com.rohitthebest.manageyourrenters.database.dao.BudgetDao
+import com.rohitthebest.manageyourrenters.database.dao.IncomeDao
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.database.typeConverters.TypeConvertersForDatabase
+
+@Database(
+ entities = [Budget::class, Income::class],
+ version = 1,
+ exportSchema = false
+)
+@TypeConverters(TypeConvertersForDatabase::class)
+abstract class BudgetAndIncomeDatabase : RoomDatabase() {
+
+ abstract fun getBudgetDao(): BudgetDao
+ abstract fun getIncomeDao(): IncomeDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/model/Budget.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/model/Budget.kt
new file mode 100644
index 00000000..a4b7d5a9
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/model/Budget.kt
@@ -0,0 +1,53 @@
+package com.rohitthebest.manageyourrenters.database.model
+
+import androidx.room.Entity
+import androidx.room.Ignore
+import androidx.room.PrimaryKey
+import com.google.firebase.firestore.Exclude
+import com.google.firebase.firestore.IgnoreExtraProperties
+
+@Entity(tableName = "budget_table")
+@IgnoreExtraProperties
+data class Budget(
+ @PrimaryKey(autoGenerate = false) var key: String,
+ var created: Long,
+ var modified: Long,
+ var budgetLimit: Double,
+ var month: Int,
+ var year: Int,
+ var expenseCategoryKey: String,
+ var monthYearString: String,
+ var isSynced: Boolean,
+ var uid: String
+) {
+
+ @Exclude
+ @Ignore
+ var categoryName: String = ""
+
+ @Exclude
+ @Ignore
+ var categoryImageUrl: String = ""
+
+ @Exclude
+ @Ignore
+ var currentExpenseAmount: Double = 0.0
+
+ constructor() : this(
+ "",
+ System.currentTimeMillis(),
+ System.currentTimeMillis(),
+ 0.0,
+ 0,
+ 0,
+ "",
+ "",
+ false,
+ ""
+ )
+
+ fun generateMonthYearString(): String {
+ return "${this.month}_${this.year}"
+ }
+
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/database/model/Income.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/database/model/Income.kt
new file mode 100644
index 00000000..13914c70
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/database/model/Income.kt
@@ -0,0 +1,67 @@
+package com.rohitthebest.manageyourrenters.database.model
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.google.firebase.firestore.IgnoreExtraProperties
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.utils.isValid
+
+@Entity(tableName = "income_table")
+@IgnoreExtraProperties
+data class Income(
+ @PrimaryKey(autoGenerate = false) var key: String,
+ var created: Long,
+ var modified: Long,
+ var source: String,
+ var income: Double,
+ var month: Int,
+ var year: Int,
+ var monthYearString: String,
+ var isSynced: Boolean,
+ var uid: String,
+ var linkedPaymentMethods: List? = null
+) {
+ constructor() : this(
+ "",
+ System.currentTimeMillis(),
+ System.currentTimeMillis(),
+ "",
+ 0.0,
+ 0,
+ 0,
+ "",
+ false,
+ "",
+ null
+ )
+
+ fun generateMonthYearString(): String {
+ return "${this.month}_${this.year}"
+ }
+
+ fun getPaymentMethodString(paymentMethodsMap: Map): String {
+
+ return if (this.linkedPaymentMethods == null) {
+ Constants.OTHER_PAYMENT_METHOD
+ } else {
+ var paymentMethods = ""
+ this.linkedPaymentMethods!!.forEachIndexed { index, pm ->
+
+ if (paymentMethodsMap[pm] != null) {
+ paymentMethods += paymentMethodsMap[pm]
+ }
+
+ if (index != this.linkedPaymentMethods!!.size - 1) {
+ paymentMethods += " | "
+ }
+ }
+
+ if (!paymentMethods.isValid()) {
+ paymentMethods = Constants.OTHER_PAYMENT_METHOD
+ }
+
+ paymentMethods
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/module/Module.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/module/Module.kt
index 11fe3a52..78d2a02d 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/module/Module.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/module/Module.kt
@@ -8,6 +8,7 @@ import com.rohitthebest.manageyourrenters.api.unsplash.UnsplashAPI
import com.rohitthebest.manageyourrenters.database.databases.*
import com.rohitthebest.manageyourrenters.others.Constants.BORROWER_DATABASE_NAME
import com.rohitthebest.manageyourrenters.others.Constants.BORROWER_PAYMENT_DATABASE_NAME
+import com.rohitthebest.manageyourrenters.others.Constants.BUDGET_AND_INCOME_DATABASE
import com.rohitthebest.manageyourrenters.others.Constants.EMI_DATABASE_NAME
import com.rohitthebest.manageyourrenters.others.Constants.EXPENSE_DATABASE_NAME
import com.rohitthebest.manageyourrenters.others.Constants.MONTHLY_PAYMENT_DATABASE_NAME
@@ -283,6 +284,29 @@ object Module {
// ==================================================================================
+ // ----------------------------- Income and Budget ----------------------------------------
+
+ @Singleton
+ @Provides
+ fun getBudgetAndIncomeDatabase(
+ @ApplicationContext context: Context
+ ) = Room.databaseBuilder(
+ context,
+ BudgetAndIncomeDatabase::class.java,
+ BUDGET_AND_INCOME_DATABASE
+ ).build()
+
+ @Provides
+ @Singleton
+ fun getBudgetDao(db: BudgetAndIncomeDatabase) = db.getBudgetDao()
+
+ @Provides
+ @Singleton
+ fun getIncomeDao(db: BudgetAndIncomeDatabase) = db.getIncomeDao()
+
+ // ==================================================================================
+
+
// ------------------------------ Expense Category API -----------------------------------
@Provides
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/others/Constants.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/others/Constants.kt
index c4f2129a..f939ee48 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/others/Constants.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/others/Constants.kt
@@ -7,7 +7,7 @@ object Constants {
const val UNSPLASH_BASE_URL = "https://api.unsplash.com/"
const val NO_INTERNET_MESSAGE = "Please check your Internet connection!!!"
- const val EDIT_TEXT_EMPTY_MESSAGE = "*This is a mandatory field...!!"
+ const val EDIT_TEXT_EMPTY_MESSAGE = "*This is a mandatory field..."
// ----- databases name RELATED---------
const val RENTER_AND_PAYMENT_DATABASE_NAME = "renter_and_payment_database.db"
@@ -18,10 +18,14 @@ object Constants {
const val EXPENSE_DATABASE_NAME = "expense_database.db"
const val MONTHLY_PAYMENT_DATABASE_NAME = "monthly_payment_db"
const val PAYMENT_METHOD_DATABASE_NAME = "payment_method_db"
+ const val BUDGET_AND_INCOME_DATABASE = "budget_income_database.db"
//----------------------------
+ //------------
const val NOTIFICATION_CHANNEL_ID = "NotificationChannelID"
+ //------------
+ //-------- Firestore----------
const val COLLECTION_KEY = "Colection_key_dskjsadaaddhadkjhbskjbvjhb"
const val DOCUMENT_KEY = "Document_key_dskjshfjksadadbskjbvjhb"
const val UPDATE_DOCUMENT_MAP_KEY = "UPDATE_DOCUMENT_MAP_KEYadsadsaDF_dskjshfdfsdadkjhbskjbvjhb"
@@ -29,16 +33,19 @@ object Constants {
const val RANDOM_ID_KEY = "RANDOM_ID_KEY_sdjhdsdvjkbvbavbhbvhjbhjbvdb"
const val KEY_LIST_KEY = "KEY_LIST_KEY_fdhvjkhjkvkjvkbvk"
const val DELETE_FILE_FROM_FIREBASE_KEY = "DELETE_FILE_FROM_FIREBASE_KEY_sjvvbibisbvbaib"
+ //-----------------------------
+ //--------- Shared Preference-----------
const val IS_SYNCED_SHARED_PREF_NAME = "IS_SYNCED_SHARED_PREF_NAME_fdkdnf"
const val IS_SYNCED_SHARED_PREF_KEY = "IS_SYNCED_SHARED_PREF_KEY_fkjdvkjdbdnf"
const val CUSTOM_DATE_RANGE_FOR_GRAPH_FRAGMENT_SHARED_PREF_NAME = "djbbbwebhbBKJBJBSHBHBJbbsjb"
const val CUSTOM_DATE_RANGE_FOR_GRAPH_FRAGMENT_SHARED_PREF_KEY = "sbjsbbhjsbhbyguqyJBABBHJBkbnd"
+ // --------------------------------------
const val SUPPORTING_DOCUMENT_HELPER_MODEL_KEY = "dsbjsbjhbuyavvu"
const val RENTER_PAYMENT_CONFIRMATION_BILL_KEY = "BNZBNJBHVDLK&hjbxjhbskxknxB"
- // Menu BottomSheet related
+ //--- Menu BottomSheet related---------
const val SHOW_EDIT_MENU = "SHOW_EDIT_MENU_sjcjwncjkn"
const val SHOW_DOCUMENTS_MENU = "SHOW_DOCUMENTS_MENU_sjcjwncacacn"
const val SHOW_DELETE_MENU = "DELETE_MENUbkjbskjc"
@@ -46,6 +53,7 @@ object Constants {
const val SHOW_MOVE_MENU = "MOVE_SAdhbhfbABJnbhsb"
const val SHOW_COPY_MENU = "COPY_SANJABABJnbhhsb"
const val COPY_MENU_TEXT = "COPY-TEXT_SwwAdsdrBAbuh=hsb"
+ //---------------------------
const val NETWORK_PAGE_SIZE_UNSPLASH = 30
const val SHORTCUT_FRAGMENT_NAME_KEY = "sdcjnsknkanckbajcbabacjk"
@@ -54,17 +62,20 @@ object Constants {
const val FILE_PROVIDER_AUTHORITY = "com.rohitthebest.manageyourrenters.provider"
+ // -------- Shortcuts ---------
const val SHORTCUT_EXPENSE = "com.rohitthebest.manageyourrenters.expense"
const val SHORTCUT_BORROWERS = "com.rohitthebest.manageyourrenters.borrowers"
const val SHORTCUT_HOUSE_RENTERS = "com.rohitthebest.manageyourrenters.house_renters"
const val SHORTCUT_MONTHLY_PAYMENTS = "com.rohitthebest.manageyourrenters.monthly_payments"
const val SHORTCUT_EMI = "com.rohitthebest.manageyourrenters.emis"
+ // ----------------------------
- // app update related
+ //--- app update related------
const val APP_VERSION = BuildConfig.VERSION_NAME
const val APP_UPDATE_FIRESTORE_DOCUMENT_KEY = "latest_app_version"
+ //------------------------------
- // Default payment method keys
+ //---- Default payment method keys-------
const val PAYMENT_METHOD_CASH_KEY = "payment_method_cash_1316797_rrrrr"
const val PAYMENT_METHOD_DEBIT_CARD_KEY = "payment_method_debit_card_5765763_rrrrr"
const val PAYMENT_METHOD_CREDIT_CARD_KEY = "payment_method_credit_card_974673_rrrrr"
@@ -74,6 +85,7 @@ object Constants {
const val OTHER_PAYMENT_METHOD = "💰 OTHER"
const val DEBIT_CARD_PAYMENT_METHOD = "💳 DEBIT CARD"
const val CREDIT_CARD_PAYMENT_METHOD = "💳 CREDIT CARD"
+ // --------------------------------------
const val ADD_PAYMENT_METHOD_KEY =
"add_payment_method_key" // will be used only for recycler view
@@ -83,9 +95,22 @@ object Constants {
const val PAYMENT_METHOD_KEY_FOR_EDIT = "PAYMENT_METHOD_KEY_FOR_EDIT_KEY"
const val EXPENSE_FILTER_KEY = "EXPENSE_FILTER_KEY_sdbjhsbjs"
+ const val BUDGET = "passing_budget_object_using_bundle_key"
const val SERVICE_STOP_TIME_IN_SECONDS: Long = 45
const val GENERIC_KEY_FOR_ACTIVITY_OR_FRAGMENT_COMMUNICATION = "GENERIC_KEY_skdjhshjsvhjs"
const val GENERIC_KEY_FOR_ACTIVITY_OR_FRAGMENT_COMMUNICATION2 =
"GENERIC_KEY_2**cnxjhshjxfnvkjss"
+
+ const val INCOME_MONTH_KEY = "hjsjhsvhjvsvyugsgfgwv"
+ const val INCOME_YEAR_KEY = "sdghjsbhjsvhshghgschgc"
+
+ //----------------------- Month and Year Picker Dialog ---------------------------
+ const val MONTH_YEAR_PICKER_MONTH_KEY = "sbhsbhjbjhdvgsfycsgfcgfwc"
+ const val MONTH_YEAR_PICKER_YEAR_KEY = "hsbjhsvhgvshgsgcfscgfcsg"
+ const val MONTH_YEAR_PICKER_MIN_YEAR_KEY = "sdhbsjhhjsvgsvghsvhgshhgs"
+ const val MONTH_YEAR_PICKER_MAX_YEAR_KEY = "jbhjsvgsygsfrtsfgctradxaa"
+ //--------------------------------------------------------------------------------
+
+ const val COPY_BUDGET_MONTH_AND_YEAR_KEY = "rdnjshbhsvygacfcfacfca"
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/others/FirestoreCollectionsConstants.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/others/FirestoreCollectionsConstants.kt
index cd89b8ee..6b54a753 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/others/FirestoreCollectionsConstants.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/others/FirestoreCollectionsConstants.kt
@@ -15,4 +15,6 @@ object FirestoreCollectionsConstants {
const val PARTIAL_PAYMENTS = "Partial_Payments"
const val APP_UPDATES = "AppUpdates"
const val PAYMENT_METHODS = "PaymentMethods"
+ const val BUDGETS = "Budgets"
+ const val INCOMES = "Incomes"
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/BudgetRepository.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/BudgetRepository.kt
new file mode 100644
index 00000000..57055831
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/BudgetRepository.kt
@@ -0,0 +1,72 @@
+package com.rohitthebest.manageyourrenters.repositories
+
+import com.rohitthebest.manageyourrenters.database.dao.BudgetDao
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import javax.inject.Inject
+
+class BudgetRepository @Inject constructor(
+ val dao: BudgetDao
+) {
+
+ suspend fun insertBudget(budget: Budget) = dao.insertBudget(budget)
+
+ suspend fun insertAllBudget(budgets: List) =
+ dao.insertAllBudget(budgets)
+
+ suspend fun updateBudget(budget: Budget) =
+ dao.updateBudget(budget)
+
+ suspend fun deleteBudget(budget: Budget) =
+ dao.deleteBudget(budget)
+
+ suspend fun deleteAllBudgets() = dao.deleteAllBudgets()
+
+ suspend fun deleteBudgetsByExpenseCategoryKey(expenseCategoryKey: String) =
+ dao.deleteBudgetsByExpenseCategoryKey(expenseCategoryKey)
+
+ suspend fun deleteBudgetsByMonthAndYear(month: Int, year: Int) =
+ dao.deleteBudgetsByMonthAndYear(month, year)
+
+ fun getAllBudgets() = dao.getAllBudgets()
+
+ fun getAllBudgetsByKey(keyList: List) = dao.getAllBudgetsByKey(keyList)
+
+ fun getAllBudgetsByMonthAndYear(month: Int, year: Int) =
+ dao.getAllBudgetsByMonthAndYear(month, year)
+
+ fun getAllBudgetsByMonthAndYearString(monthYearString: String) =
+ dao.getAllBudgetsByMonthAndYearString(monthYearString)
+
+ fun getTheOldestSavedBudgetYear() = dao.getTheOldestSavedBudgetYear()
+
+ fun getBudgetByKey(budgetKey: String) = dao.getBudgetBuyBudgetKey(budgetKey)
+
+ fun getTotalBudgetByMonthAndYear(month: Int, year: Int) =
+ dao.getTotalBudgetByMonthAndYear(month, year)
+
+ fun getAllTotalBudgetByYear(year: Int) = dao.getAllTotalBudgetByYear(year)
+
+ fun getExpenseCategoryKeysOfAllBudgetsByMonthAndYear(month: Int, year: Int) =
+ dao.getExpenseCategoryKeysOfAllBudgetsByMonthAndYear(
+ month, year
+ )
+
+ suspend fun getKeysByExpenseCategoryKey(expenseCategoryKey: String) =
+ dao.getKeysByExpenseCategoryKey(
+ expenseCategoryKey
+ )
+
+ suspend fun getKeysByMonthAndYear(month: Int, year: Int) = dao.getKeysByMonthAndYear(
+ month, year
+ )
+
+ suspend fun deleteByIsSyncedValue(isSynced: Boolean) = dao.deleteByIsSyncedValue(isSynced)
+
+ fun getAllBudgetMonthAndYearForWhichBudgetIsAdded() =
+ dao.getAllBudgetMonthAndYearForWhichBudgetIsAdded()
+
+ fun isAnyBudgetAddedForThisMonthAndYear(monthYearString: String) =
+ dao.isAnyBudgetAddedForThisMonthAndYear(
+ monthYearString
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseCategoryRepository.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseCategoryRepository.kt
index 110bb702..2a05b295 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseCategoryRepository.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseCategoryRepository.kt
@@ -26,7 +26,10 @@ class ExpenseCategoryRepository @Inject constructor(
dao.deleteAllExpenseCategoriesByIsSynced(isSynced)
fun getAllExpenseCategories() = dao.getAllExpenseCategories()
+ fun getExpenseCategoriesByKey(expenseCategoryKeys: List) =
+ dao.getExpenseCategoriesByKey(expenseCategoryKeys)
fun getExpenseCategoryByKey(key: String) = dao.getExpenseCategoryByKey(key)
+ fun getAllExpenseCategoriesByLimit(limit: Int) = dao.getAllExpenseCategoriesByLimit(limit)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseRepository.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseRepository.kt
index 0a27acd5..763f5ceb 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseRepository.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/ExpenseRepository.kt
@@ -2,6 +2,7 @@ package com.rohitthebest.manageyourrenters.repositories
import com.rohitthebest.manageyourrenters.database.dao.ExpenseDAO
import com.rohitthebest.manageyourrenters.database.model.Expense
+import com.rohitthebest.manageyourrenters.others.Constants
import javax.inject.Inject
class ExpenseRepository @Inject constructor(
@@ -63,6 +64,22 @@ class ExpenseRepository @Inject constructor(
) =
dao.getTotalExpenseAmountByCategoryKeyAndDateRange(expenseCategoryKey, date1, date2)
+ fun getTotalExpenseByCategoryKeys(
+ expenseCategoryKeys: List
+ ) = dao.getTotalExpenseByCategoryKeys(expenseCategoryKeys)
+
+ fun getTotalExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ) = dao.getTotalExpenseByCategoryKeysAndDateRange(expenseCategoryKeys, date1, date2)
+
+ fun getExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ) = dao.getExpenseByCategoryKeysAndDateRange(expenseCategoryKeys, date1, date2)
+
fun getExpenseByDateRangeAndExpenseCategoryKey(
expenseCategoryKey: String, date1: Long, date2: Long
) = dao.getExpenseByDateRangeAndExpenseCategoryKey(expenseCategoryKey, date1, date2)
@@ -80,4 +97,62 @@ class ExpenseRepository @Inject constructor(
suspend fun getKeysByExpenseCategoryKey(expenseCategoryKey: String) =
dao.getKeysByExpenseCategoryKey(expenseCategoryKey)
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeys() =
+ dao.getTotalExpenseAmountsWithTheirExpenseCategoryKeys()
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByListOfExpenseKeys(
+ expenseKeys: List
+ ) = dao.getTotalExpenseAmountsWithTheirExpenseCategoryKeysByListOfExpenseKeys(expenseKeys)
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRange(date1: Long, date2: Long) =
+ dao.getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRange(date1, date2)
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRangeAndByListOfExpenseKeys(
+ date1: Long,
+ date2: Long,
+ expenseKeys: List
+ ) = dao.getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRangeAndByListOfExpenseKeys(
+ date1, date2, expenseKeys
+ )
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategories(
+ selectedExpenseCategories: List
+ ) = dao.getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategories(
+ selectedExpenseCategories
+ )
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategoriesByDateRange(
+ selectedExpenseCategories: List,
+ date1: Long,
+ date2: Long
+ ) =
+ dao.getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategoriesByDateRange(
+ selectedExpenseCategories,
+ date1,
+ date2
+ )
+
+ fun isAnyExpenseAdded() = dao.isAnyExpenseAdded()
+
+ fun applyExpenseFilterByPaymentMethods(
+ paymentMethodKeys: List,
+ expenses: List
+ ): List {
+
+ val isOtherPaymentMethodKeyPresent =
+ paymentMethodKeys.contains(Constants.PAYMENT_METHOD_OTHER_KEY)
+
+ val resultExpenses = expenses.filter { expense ->
+
+ if (isOtherPaymentMethodKeyPresent) {
+ // for other payment method, get all the expenses where payment methods is null as well as payment method is other
+ expense.paymentMethods == null || expense.paymentMethods!!.any { it in paymentMethodKeys }
+ } else {
+ expense.paymentMethods != null && expense.paymentMethods!!.any { it in paymentMethodKeys }
+ }
+ }
+
+ return resultExpenses
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/IncomeRepository.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/IncomeRepository.kt
new file mode 100644
index 00000000..7ffaf97c
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/IncomeRepository.kt
@@ -0,0 +1,61 @@
+package com.rohitthebest.manageyourrenters.repositories
+
+import com.rohitthebest.manageyourrenters.database.dao.IncomeDao
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.others.Constants
+import javax.inject.Inject
+
+class IncomeRepository @Inject constructor(
+ val dao: IncomeDao
+) {
+
+ suspend fun insertIncome(income: Income) = dao.insertIncome(income)
+
+ suspend fun insertAllIncome(incomes: List) =
+ dao.insertAllIncome(incomes)
+
+ suspend fun updateIncome(income: Income) =
+ dao.updateIncome(income)
+
+ suspend fun deleteIncome(income: Income) =
+ dao.deleteIncome(income)
+
+ suspend fun deleteAllIncomes() = dao.deleteAllIncomes()
+
+ fun getAllIncomes() = dao.getAllIncomes()
+
+ fun getAllIncomesByMonthAndYear(month: Int, year: Int) =
+ dao.getAllIncomesByMonthAndYear(month, year)
+
+ fun getIncomeByKey(incomeKey: String) = dao.getIncomeByKey(incomeKey)
+
+ fun getTotalIncomeAddedByMonthAndYear(month: Int, year: Int) =
+ dao.getTotalIncomeAddedByMonthAndYear(month, year)
+
+ fun getAllTotalIncomeByYear(year: Int) = dao.getAllTotalIncomeByYear(year)
+
+ fun getAllIncomeSources() = dao.getAllIncomeSources()
+ suspend fun deleteByIsSyncedValue(isSynced: Boolean) = dao.deleteByIsSyncedValue(isSynced)
+
+ fun applyFilterByPaymentMethods(
+ paymentMethodKeys: List,
+ incomes: List
+ ): List {
+
+ val isOtherPaymentMethodKeyPresent =
+ paymentMethodKeys.contains(Constants.PAYMENT_METHOD_OTHER_KEY)
+
+ val resultIncomes = incomes.filter { income ->
+
+ if (isOtherPaymentMethodKeyPresent) {
+ // for other payment method, get all the expenses where payment methods is null as well as payment method is other
+ income.linkedPaymentMethods == null || income.linkedPaymentMethods!!.any { it in paymentMethodKeys }
+ } else {
+ income.linkedPaymentMethods != null && income.linkedPaymentMethods!!.any { it in paymentMethodKeys }
+ }
+ }
+
+ return resultIncomes
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/RenterRepository.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/RenterRepository.kt
index 3900e4a1..1c27391b 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/RenterRepository.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/RenterRepository.kt
@@ -37,4 +37,6 @@ class RenterRepository @Inject constructor(
) = dao.getRentersWithTheirAmountPaidByDateCreated(startDate, endDate)
fun getAllDistinctAddress() = dao.getAllDistinctAddress()
+
+ fun getRentersWithTheirDues() = dao.getRentersWithTheirDues()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/UpdateIsSyncedValueForAnyTable.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/UpdateIsSyncedValueForAnyTable.kt
index 9a076efd..7f257205 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/UpdateIsSyncedValueForAnyTable.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/repositories/UpdateIsSyncedValueForAnyTable.kt
@@ -1,6 +1,18 @@
package com.rohitthebest.manageyourrenters.repositories
-import com.rohitthebest.manageyourrenters.database.dao.*
+import com.rohitthebest.manageyourrenters.database.dao.BorrowerDao
+import com.rohitthebest.manageyourrenters.database.dao.BorrowerPaymentDao
+import com.rohitthebest.manageyourrenters.database.dao.BudgetDao
+import com.rohitthebest.manageyourrenters.database.dao.EMIDao
+import com.rohitthebest.manageyourrenters.database.dao.EMIPaymentDao
+import com.rohitthebest.manageyourrenters.database.dao.ExpenseCategoryDAO
+import com.rohitthebest.manageyourrenters.database.dao.ExpenseDAO
+import com.rohitthebest.manageyourrenters.database.dao.IncomeDao
+import com.rohitthebest.manageyourrenters.database.dao.MonthlyPaymentCategoryDao
+import com.rohitthebest.manageyourrenters.database.dao.MonthlyPaymentDao
+import com.rohitthebest.manageyourrenters.database.dao.PaymentMethodDao
+import com.rohitthebest.manageyourrenters.database.dao.RenterDao
+import com.rohitthebest.manageyourrenters.database.dao.RenterPaymentDao
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
import javax.inject.Inject
@@ -15,7 +27,9 @@ class UpdateIsSyncedValueForAnyTableRepository @Inject constructor(
private val expenseDAO: ExpenseDAO,
private val expenseCategoryDAO: ExpenseCategoryDAO,
private val monthlyPaymentDao: MonthlyPaymentDao,
- private val monthlyPaymentCategoryDAO: MonthlyPaymentCategoryDao
+ private val monthlyPaymentCategoryDAO: MonthlyPaymentCategoryDao,
+ private val budgetDao: BudgetDao,
+ private val incomeDao: IncomeDao
) {
suspend fun updateIsSyncValueToFalse(collection: String, key: String) {
@@ -64,6 +78,14 @@ class UpdateIsSyncedValueForAnyTableRepository @Inject constructor(
FirestoreCollectionsConstants.PAYMENT_METHODS -> {
paymentMethodDao.updateIsSyncedValueToFalse(key)
}
+
+ FirestoreCollectionsConstants.BUDGETS -> {
+ budgetDao.updateIsSyncedValueToFalse(key)
+ }
+
+ FirestoreCollectionsConstants.INCOMES -> {
+ incomeDao.updateIsSyncedValueToFalse(key)
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/services/SyncDocumentsFromFirestoreService.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/services/SyncDocumentsFromFirestoreService.kt
index 656cc796..5d6773c7 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/services/SyncDocumentsFromFirestoreService.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/services/SyncDocumentsFromFirestoreService.kt
@@ -7,14 +7,44 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import com.rohitthebest.manageyourrenters.R
import com.rohitthebest.manageyourrenters.data.BillPeriodType
-import com.rohitthebest.manageyourrenters.database.model.*
+import com.rohitthebest.manageyourrenters.database.model.Borrower
+import com.rohitthebest.manageyourrenters.database.model.BorrowerPayment
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.database.model.EMI
+import com.rohitthebest.manageyourrenters.database.model.EMIPayment
+import com.rohitthebest.manageyourrenters.database.model.Expense
+import com.rohitthebest.manageyourrenters.database.model.ExpenseCategory
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.database.model.MonthlyPayment
+import com.rohitthebest.manageyourrenters.database.model.MonthlyPaymentCategory
+import com.rohitthebest.manageyourrenters.database.model.PartialPayment
+import com.rohitthebest.manageyourrenters.database.model.PaymentMethod
+import com.rohitthebest.manageyourrenters.database.model.Renter
+import com.rohitthebest.manageyourrenters.database.model.RenterPayment
import com.rohitthebest.manageyourrenters.others.Constants
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
-import com.rohitthebest.manageyourrenters.repositories.*
+import com.rohitthebest.manageyourrenters.repositories.BorrowerPaymentRepository
+import com.rohitthebest.manageyourrenters.repositories.BorrowerRepository
+import com.rohitthebest.manageyourrenters.repositories.BudgetRepository
+import com.rohitthebest.manageyourrenters.repositories.EMIPaymentRepository
+import com.rohitthebest.manageyourrenters.repositories.EMIRepository
+import com.rohitthebest.manageyourrenters.repositories.ExpenseCategoryRepository
+import com.rohitthebest.manageyourrenters.repositories.ExpenseRepository
+import com.rohitthebest.manageyourrenters.repositories.IncomeRepository
+import com.rohitthebest.manageyourrenters.repositories.MonthlyPaymentCategoryRepository
+import com.rohitthebest.manageyourrenters.repositories.MonthlyPaymentRepository
+import com.rohitthebest.manageyourrenters.repositories.PartialPaymentRepository
+import com.rohitthebest.manageyourrenters.repositories.PaymentMethodRepository
+import com.rohitthebest.manageyourrenters.repositories.RenterPaymentRepository
+import com.rohitthebest.manageyourrenters.repositories.RenterRepository
import com.rohitthebest.manageyourrenters.utils.Functions
import com.rohitthebest.manageyourrenters.utils.getDataFromFireStore
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -59,6 +89,12 @@ class SyncDocumentsFromFirestoreService : Service() {
@Inject
lateinit var paymentMethodRepository: PaymentMethodRepository
+ @Inject
+ lateinit var budgetRepository: BudgetRepository
+
+ @Inject
+ lateinit var incomeRepository: IncomeRepository
+
private var uid = ""
@@ -90,11 +126,28 @@ class SyncDocumentsFromFirestoreService : Service() {
syncPaymentMethods()
}
+ val job7 = CoroutineScope(Dispatchers.IO).launch {
+ syncBudgets()
+ }
+
+ val job8 = CoroutineScope(Dispatchers.IO).launch {
+ syncIncomes()
+ }
+
+
CoroutineScope(Dispatchers.IO).launch {
delay(50)
- while (!job1.isCompleted || !job2.isCompleted || !job3.isCompleted || !job4.isCompleted || !job5.isCompleted || !job6.isCompleted) {
+ while (!job1.isCompleted
+ || !job2.isCompleted
+ || !job3.isCompleted
+ || !job4.isCompleted
+ || !job5.isCompleted
+ || !job6.isCompleted
+ || !job7.isCompleted
+ || !job8.isCompleted
+ ) {
delay(100)
Log.d(TAG, "onStartCommand: sync time extended")
@@ -390,6 +443,45 @@ class SyncDocumentsFromFirestoreService : Service() {
}
}
+ private suspend fun syncBudgets() {
+
+ getDataFromFireStore(
+ FirestoreCollectionsConstants.BUDGETS,
+ uid
+ ) {}?.let { budgetSnapshot ->
+
+ if (budgetSnapshot.size() != 0) {
+
+ budgetRepository.deleteByIsSyncedValue(true)
+ delay(50)
+ budgetRepository.insertAllBudget(
+ budgetSnapshot.toObjects(
+ Budget::class.java
+ )
+ )
+ }
+ }
+ }
+
+ private suspend fun syncIncomes() {
+
+ getDataFromFireStore(
+ FirestoreCollectionsConstants.INCOMES,
+ uid
+ ) {}?.let { incomeSnapshot ->
+
+ if (incomeSnapshot.size() != 0) {
+
+ incomeRepository.deleteByIsSyncedValue(true)
+ delay(50)
+ incomeRepository.insertAllIncome(
+ incomeSnapshot.toObjects(
+ Income::class.java
+ )
+ )
+ }
+ }
+ }
override fun onBind(intent: Intent?): IBinder? {
return null
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UpdateService.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UpdateService.kt
index 8184ef56..a38c3456 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UpdateService.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UpdateService.kt
@@ -16,6 +16,7 @@ import com.rohitthebest.manageyourrenters.others.Constants.RANDOM_ID_KEY
import com.rohitthebest.manageyourrenters.others.Constants.UPDATE_DOCUMENT_MAP_KEY
import com.rohitthebest.manageyourrenters.repositories.UpdateIsSyncedValueForAnyTableRepository
import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.convertJsonToObject
import com.rohitthebest.manageyourrenters.utils.updateDocumentOnFireStore
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@@ -38,12 +39,7 @@ class UpdateService : Service() {
val collection = intent?.getStringExtra(COLLECTION_KEY)
val key = intent?.getStringExtra(DOCUMENT_KEY)
- val map: HashMap =
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
- intent?.getSerializableExtra(UPDATE_DOCUMENT_MAP_KEY) as HashMap
- } else ({
- intent?.getSerializableExtra(UPDATE_DOCUMENT_MAP_KEY, HashMap::class.java)
- }) as HashMap
+ val map = intent?.getStringExtra(UPDATE_DOCUMENT_MAP_KEY)?.convertJsonToObject(HashMap::class.java) as HashMap
val randomId = intent?.getIntExtra(RANDOM_ID_KEY, 1000)
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListService.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListService.kt
new file mode 100644
index 00000000..a78e67d8
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListService.kt
@@ -0,0 +1,4 @@
+package com.rohitthebest.manageyourrenters.services
+
+private const val TAG = "UploadDocumentListService"
+
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListToFireStoreService.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListToFireStoreService.kt
index 1ad53d6b..3d3383ae 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListToFireStoreService.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadDocumentListToFireStoreService.kt
@@ -7,32 +7,59 @@ import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.WriteBatch
import com.rohitthebest.manageyourrenters.R
import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BORROWER_PAYMENTS
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BUDGETS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.PARTIAL_PAYMENTS
+import com.rohitthebest.manageyourrenters.repositories.BudgetRepository
+import com.rohitthebest.manageyourrenters.repositories.UpdateIsSyncedValueForAnyTableRepository
import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.convertJSONToStringList
import com.rohitthebest.manageyourrenters.utils.fromStringToPartialPaymentList
import com.rohitthebest.manageyourrenters.utils.uploadFilesOnFirestore
+import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
+import javax.inject.Inject
private const val TAG = "UploadDocumentListToFir"
+@AndroidEntryPoint
class UploadDocumentListToFireStoreService : Service() {
+ @Inject
+ lateinit var budgetRepository: BudgetRepository
+
+ @Inject
+ lateinit var updateIsSyncedValueForAnyTableRepository: UpdateIsSyncedValueForAnyTableRepository
+
+ private lateinit var db: FirebaseFirestore
+ private lateinit var batch: WriteBatch
+
+ private var isUploadSuccessful = false
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val collection = intent?.getStringExtra(Constants.COLLECTION_KEY)
+ // this is used when list of objects need to be uploaded directly without db interaction (ex: partialPayments)
val uploadData = intent?.getStringExtra(Constants.UPLOAD_DATA_KEY)
- val randomId = intent?.getIntExtra(Constants.RANDOM_ID_KEY, 1003)
+ // this is used when only list of keys are passed and objects are retrieved through db here (ex: budgets)
+ val keys = intent?.getStringExtra(Constants.KEY_LIST_KEY)
+ val randomId = intent?.getIntExtra(Constants.RANDOM_ID_KEY, 2003)
+
+ db = FirebaseFirestore.getInstance()
+ batch = db.batch()
val pendingIntent: PendingIntent =
Functions.getPendingIntentForForegroundServiceNotification(
this,
- if (collection == getString(R.string.partialPayments)) getString(R.string.borrowerPayments) else collection
+ if (collection == PARTIAL_PAYMENTS) BORROWER_PAYMENTS else collection
?: ""
)
@@ -44,12 +71,33 @@ class UploadDocumentListToFireStoreService : Service() {
).setSmallIcon(image)
.setContentIntent(pendingIntent)
.setContentTitle("Uploading list to the $collection...")
+ .setProgress(100, 0, true)
.build()
startForeground(randomId!!, notification)
- val db = FirebaseFirestore.getInstance()
- val batch = db.batch()
+ val keyList = if (collection != PARTIAL_PAYMENTS) {
+ convertJSONToStringList(keys)
+ } else {
+ emptyList()
+ }
+
+ // starting stop timer
+ val stopTimerJob = CoroutineScope(Dispatchers.IO).launch {
+
+ Log.d(
+ TAG,
+ "onStartCommand: timer started for ${Constants.SERVICE_STOP_TIME_IN_SECONDS} seconds"
+ )
+ delay(TimeUnit.SECONDS.toMillis(Constants.SERVICE_STOP_TIME_IN_SECONDS))
+
+ if (!isUploadSuccessful && collection != PARTIAL_PAYMENTS) {
+
+ updateIsSyncedValueToFalse(collection!!, keyList)
+ }
+
+ stopSelf()
+ }
CoroutineScope(Dispatchers.IO).launch {
@@ -64,42 +112,81 @@ class UploadDocumentListToFireStoreService : Service() {
if (partialPaymentList.isNotEmpty()) {
partialPaymentList.forEach { partialPayment ->
-
batch.set(
- db.collection(collection)
- .document(partialPayment.key),
+ db.collection(collection).document(partialPayment.key),
partialPayment
)
}
-
if (uploadFilesOnFirestore(batch)) {
-
Log.d(TAG, "onStartCommand: List Upload SUCCESSFUL")
+ isUploadSuccessful = true
+ stopTimerJob.cancel()
stopSelf()
} else {
Log.d(TAG, "onStartCommand: List Upload UNSUCCESSFUL")
+ stopTimerJob.cancel()
stopSelf()
}
} else {
+ stopTimerJob.cancel()
+ stopSelf()
+ }
+ }
+
+ BUDGETS -> {
+
+ if (keyList.isNotEmpty()) {
+ handleBudgetList(keyList, collection)
+ } else {
+ stopTimerJob.cancel()
stopSelf()
}
}
+
+ else -> {
+ stopTimerJob.cancel()
+ stopSelf()
+ }
}
}
+ return START_NOT_STICKY
+ }
- // starting stop timer
- CoroutineScope(Dispatchers.IO).launch {
+ private suspend fun handleBudgetList(keyList: List, collection: String) {
- Log.d(
- TAG,
- "onStartCommand: timer started for ${Constants.SERVICE_STOP_TIME_IN_SECONDS} seconds"
- )
- delay(TimeUnit.SECONDS.toMillis(Constants.SERVICE_STOP_TIME_IN_SECONDS))
- stopSelf()
+ val budgetModelList = budgetRepository.getAllBudgetsByKey(keyList).first()
+
+ if (budgetModelList.isNotEmpty()) {
+
+ budgetModelList.forEach { budget ->
+ batch.set(db.collection(collection).document(budget.key), budget)
+ }
+
+ if (uploadFilesOnFirestore(batch)) {
+
+ Log.d(TAG, "onStartCommand: Budget List Upload SUCCESSFUL")
+ isUploadSuccessful = true
+ stopSelf()
+ } else {
+ Log.d(TAG, "onStartCommand: Budget List Upload UNSUCCESSFUL")
+
+ updateIsSyncedValueToFalse(collection, keyList)
+ stopSelf()
+ }
}
+ }
- return START_NOT_STICKY
+ private suspend fun updateIsSyncedValueToFalse(collection: String, keyList: List) {
+
+ keyList.forEach { key ->
+
+ key?.let {
+ updateIsSyncedValueForAnyTableRepository.updateIsSyncValueToFalse(
+ collection, key
+ )
+ }
+ }
}
override fun onBind(intent: Intent?): IBinder? {
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadService.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadService.kt
index 9861761b..04275542 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadService.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/services/UploadService.kt
@@ -17,10 +17,12 @@ import com.rohitthebest.manageyourrenters.others.Constants.RANDOM_ID_KEY
import com.rohitthebest.manageyourrenters.others.Constants.SERVICE_STOP_TIME_IN_SECONDS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BORROWERS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BORROWER_PAYMENTS
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BUDGETS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EMI_PAYMENTS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EMIs
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EXPENSES
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EXPENSE_CATEGORIES
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.INCOMES
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.MONTHLY_PAYMENTS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.MONTHLY_PAYMENT_CATEGORIES
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.PAYMENT_METHODS
@@ -79,6 +81,12 @@ class UploadService : Service() {
@Inject
lateinit var updateIsSyncedValueForAnyTableRepository: UpdateIsSyncedValueForAnyTableRepository
+ @Inject
+ lateinit var budgetRepository: BudgetRepository
+
+ @Inject
+ lateinit var incomeRepository: IncomeRepository
+
@SuppressLint("UnspecifiedImmutableFlag")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -180,6 +188,15 @@ class UploadService : Service() {
model = paymentMethodRepository.getPaymentMethodByKey(key).first()
}
+ BUDGETS -> {
+
+ model = budgetRepository.getBudgetByKey(key).first()
+ }
+
+ INCOMES -> {
+ model = incomeRepository.getIncomeByKey(key).first()
+ }
+
else -> {
stopTimerJob.cancel()
stopSelf()
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/todo.txt b/app/src/main/java/com/rohitthebest/manageyourrenters/todo.txt
new file mode 100644
index 00000000..0618d4a4
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/todo.txt
@@ -0,0 +1,27 @@
+
+- Also in BudgetAndIncomeOverviewFragment, for all the budget, also show message, like,
+
+ * Starting (Early Warning Stage):
+
+ == "Start Tracking Your Spending Now!": Encouraging users to begin monitoring their expenses early on to avoid surprises later.
+ == "Stay Ahead of Your Budget": Setting a proactive tone, reminding users to be mindful of their spending from the beginning.
+ == "It's Easier to Stay Within Budget from the Start": Highlighting the advantage of early budget adherence for better financial control.
+
+ * Middle (Approaching the Budget Limit):
+ == "You're Doing Well, But Be Cautious": Acknowledging their efforts to stick to the budget while urging caution as they get closer to the limit.
+ == "Your Spending Is Getting Closer to the Limit": Providing a straightforward update on their proximity to the budget cap.
+ == "Keep an Eye on Your Remaining Budget": Advising users to keep track of their remaining funds to avoid exceeding their limit.
+
+ * End (Near Budget Limit):
+ == "Alert: You're Approaching Your Budget Limit!": Using stronger language to grab their attention and indicate a critical stage.
+ == "Last Few Dollars Left – Spend Wisely": Reminding users that they have limited funds remaining and should use them judiciously.
+ == "You're Almost at Your Budget Limit": Clearly informing users that they are nearing the end of their budget.
+
+ * Budget Exceeded:
+ == "Oops! You've Exceeded Your Budget": Sympathetic message to acknowledge when they've gone over the budget.
+ == "Time to Review Your Expenses": Encouraging users to reevaluate their spending habits and adjust accordingly.
+ == "Learn from This Month and Plan Better Next Time": Encouraging a positive outlook and improvement for future budgeting.
+
+// done
+- click on budge to show overview of a particular budget
+- create a fragment for showing the overview
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/activities/HomeActivity.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/activities/HomeActivity.kt
index ffb502dc..8407a894 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/activities/HomeActivity.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/activities/HomeActivity.kt
@@ -8,6 +8,7 @@ import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.gms.auth.api.signin.GoogleSignIn
@@ -32,6 +33,7 @@ import com.rohitthebest.manageyourrenters.others.Constants.SHORTCUT_FRAGMENT_NAM
import com.rohitthebest.manageyourrenters.others.Constants.SHORTCUT_HOUSE_RENTERS
import com.rohitthebest.manageyourrenters.others.Constants.SHORTCUT_MONTHLY_PAYMENTS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.APP_UPDATES
+import com.rohitthebest.manageyourrenters.services.SyncDocumentsFromFirestoreService
import com.rohitthebest.manageyourrenters.ui.ProfileBottomSheet
import com.rohitthebest.manageyourrenters.ui.viewModels.*
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAvailable
@@ -228,15 +230,19 @@ class HomeActivity : AppCompatActivity(), RenterTypeAdapter.OnClickListener,
SHORTCUT_EXPENSE -> {
onClick(binding.shortcutExpenses)
}
+
SHORTCUT_MONTHLY_PAYMENTS -> {
onClick(binding.shortcutMonthlyPayments)
}
+
SHORTCUT_EMI -> {
onClick(binding.shortcutEmis)
}
+
SHORTCUT_HOUSE_RENTERS -> {
onItemClick(RenterTypes(1, "", 0))
}
+
SHORTCUT_BORROWERS -> {
onItemClick(RenterTypes(2, "", 0))
}
@@ -386,7 +392,7 @@ class HomeActivity : AppCompatActivity(), RenterTypeAdapter.OnClickListener,
if (isInternetAvailable(this)) {
- changeIsSyncedValue()
+ changeIsSyncedValueToFalse(syncValuesAgain = true)
d.dismiss()
} else {
@@ -481,14 +487,15 @@ class HomeActivity : AppCompatActivity(), RenterTypeAdapter.OnClickListener,
expenseCategoryViewModel.deleteAllExpenseCategories()
monthlyPaymentCategoryViewModel.deleteAllMonthlyPaymentCategories()
paymentMethodViewModel.deleteAllPaymentMethods()
- changeIsSyncedValue()
+
+ changeIsSyncedValueToFalse(syncValuesAgain = false)
} catch (e: Exception) {
e.printStackTrace()
}
}
- private fun changeIsSyncedValue() {
+ private fun changeIsSyncedValueToFalse(syncValuesAgain: Boolean = false) {
try {
@@ -507,7 +514,11 @@ class HomeActivity : AppCompatActivity(), RenterTypeAdapter.OnClickListener,
withContext(Dispatchers.Main) {
- navigateToLoginActivity()
+ if (syncValuesAgain) {
+ syncDataFromFirestore()
+ } else {
+ navigateToLoginActivity()
+ }
}
}
@@ -515,4 +526,16 @@ class HomeActivity : AppCompatActivity(), RenterTypeAdapter.OnClickListener,
Log.e(TAG, "saveData: ${e.message}")
}
}
+
+ private fun syncDataFromFirestore() {
+
+ showToast(getString(R.string.syncing_values_now))
+
+ val foregroundService = Intent(
+ applicationContext,
+ SyncDocumentsFromFirestoreService::class.java
+ )
+ ContextCompat.startForegroundService(applicationContext, foregroundService)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/MonthAndYearPickerDialog.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/MonthAndYearPickerDialog.kt
new file mode 100644
index 00000000..42439b24
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/MonthAndYearPickerDialog.kt
@@ -0,0 +1,156 @@
+package com.rohitthebest.manageyourrenters.ui.fragments
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.DialogFragment
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.databinding.DialogMonthAndYearBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MonthAndYearPickerDialog : DialogFragment(R.layout.dialog_month_and_year) {
+
+ private var _binding: DialogMonthAndYearBinding? = null
+ private val binding get() = _binding!!
+
+ private var monthList: List = emptyList()
+ private var mListener: OnMonthAndYearDialogDismissListener? = null
+
+ private var selectedMonth = 0
+ private var selectedYear = 2023
+ private var minimumYear = 2000
+ private var maximumYear = 2099
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ _binding = DialogMonthAndYearBinding.bind(view)
+
+ monthList = resources.getStringArray(R.array.months).toList()
+
+ selectedMonth = WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = WorkingWithDateAndTime.getCurrentYear()
+ maximumYear = WorkingWithDateAndTime.getCurrentYear()
+
+ initListeners()
+ initUI()
+ getMessage()
+ }
+
+ private fun getMessage() {
+
+ try {
+ if (!arguments?.isEmpty!!) {
+
+ arguments?.let { bundle ->
+
+ selectedMonth = bundle.getInt(
+ Constants.MONTH_YEAR_PICKER_MONTH_KEY,
+ WorkingWithDateAndTime.getCurrentMonth()
+ )
+ selectedYear = bundle.getInt(
+ Constants.MONTH_YEAR_PICKER_YEAR_KEY,
+ WorkingWithDateAndTime.getCurrentYear()
+ )
+
+ minimumYear = bundle.getInt(
+ Constants.MONTH_YEAR_PICKER_MIN_YEAR_KEY,
+ 2000
+ )
+
+ maximumYear = bundle.getInt(
+ Constants.MONTH_YEAR_PICKER_MAX_YEAR_KEY,
+ WorkingWithDateAndTime.getCurrentYear()
+ )
+
+ initUI()
+ }
+ }
+ } catch (e: Exception) {
+ showToast(requireContext(), getString(R.string.something_went_wrong))
+ dismissDialog(false)
+ }
+ }
+
+ private fun initUI() {
+
+ binding.monthPicker.minValue = 0
+ binding.monthPicker.maxValue = 11
+ binding.monthPicker.displayedValues = monthList.toTypedArray()
+
+ binding.yearPicker.minValue = minimumYear
+ binding.yearPicker.maxValue = maximumYear
+
+ binding.monthPicker.value = selectedMonth
+ binding.yearPicker.value = selectedYear
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.setNavigationOnClickListener {
+
+ dismissDialog(false)
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_save_btn).apply {
+ this.icon = ContextCompat.getDrawable(requireContext(), R.drawable.baseline_check_24)
+
+ this.setOnMenuItemClickListener {
+
+ selectedMonth = binding.monthPicker.value
+ selectedYear = binding.yearPicker.value
+
+ dismissDialog(true, selectedMonth, selectedYear)
+
+ false
+ }
+ }
+
+ }
+
+ private fun dismissDialog(isMonthAndYearSelected: Boolean, month: Int = 0, year: Int = 0) {
+
+ if (mListener != null) {
+ mListener!!.onMonthAndYearDialogDismissed(isMonthAndYearSelected, month, year)
+ }
+
+ dismiss()
+ }
+
+ interface OnMonthAndYearDialogDismissListener {
+
+ fun onMonthAndYearDialogDismissed(
+ isMonthAndYearSelected: Boolean,
+ selectedMonth: Int,
+ selectedYear: Int
+ )
+ }
+
+ fun setOnMonthAndYearDialogDismissListener(listener: OnMonthAndYearDialogDismissListener) {
+
+ mListener = listener
+ }
+
+
+ companion object {
+ @JvmStatic
+ fun newInstance(bundle: Bundle): MonthAndYearPickerDialog {
+ val fragment = MonthAndYearPickerDialog()
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+
+ _binding = null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/borrower/AddPartialPaymentFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/borrower/AddPartialPaymentFragment.kt
index 1eb300c3..c263d3f5 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/borrower/AddPartialPaymentFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/borrower/AddPartialPaymentFragment.kt
@@ -24,6 +24,7 @@ import com.rohitthebest.manageyourrenters.database.model.PartialPayment
import com.rohitthebest.manageyourrenters.databinding.AddPartialPaymentLayoutBinding
import com.rohitthebest.manageyourrenters.databinding.FragmentAddPartialPaymentBinding
import com.rohitthebest.manageyourrenters.others.Constants.EDIT_TEXT_EMPTY_MESSAGE
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
import com.rohitthebest.manageyourrenters.ui.viewModels.BorrowerPaymentViewModel
import com.rohitthebest.manageyourrenters.ui.viewModels.PartialPaymentViewModel
import com.rohitthebest.manageyourrenters.utils.*
@@ -444,7 +445,7 @@ class AddPartialPaymentFragment : BottomSheetDialogFragment(),
uploadListOfDataToFireStore(
requireContext(),
- collection = getString(R.string.partialPayments),
+ collection = FirestoreCollectionsConstants.PARTIAL_PAYMENTS,
fromPartialPaymentListToString(notSyncedList)
)
}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/houseRenters/HomeFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/houseRenters/HomeFragment.kt
index 71dfd5c1..e41f5a2c 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/houseRenters/HomeFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/houseRenters/HomeFragment.kt
@@ -32,7 +32,7 @@ import com.rohitthebest.manageyourrenters.ui.activities.ShowImageActivity
import com.rohitthebest.manageyourrenters.ui.fragments.CustomMenuItems
import com.rohitthebest.manageyourrenters.ui.fragments.SupportingDocumentDialogFragment
import com.rohitthebest.manageyourrenters.ui.viewModels.RenterViewModel
-import com.rohitthebest.manageyourrenters.utils.*
+import com.rohitthebest.manageyourrenters.utils.Functions
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.getMillisecondsOfStartAndEndUsingConstants
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.getPairOfDateInMillisInStringInDateString
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.hideKeyBoard
@@ -40,11 +40,24 @@ import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAv
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showDateRangePickerDialog
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showNoInternetMessage
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.changeVisibilityOfFABOnScrolled
+import com.rohitthebest.manageyourrenters.utils.convertRenterToJSONString
+import com.rohitthebest.manageyourrenters.utils.convertToJsonString
+import com.rohitthebest.manageyourrenters.utils.deleteFileFromFirebaseStorage
+import com.rohitthebest.manageyourrenters.utils.executeAfterDelay
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.isValid
+import com.rohitthebest.manageyourrenters.utils.onTextChanged
+import com.rohitthebest.manageyourrenters.utils.onTextSubmit
+import com.rohitthebest.manageyourrenters.utils.show
+import com.rohitthebest.manageyourrenters.utils.showAlertDialogForDeletion
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import java.util.*
+import java.util.Locale
+import kotlin.math.abs
private const val TAG = "HomeFragment"
@@ -68,6 +81,9 @@ class HomeFragment : Fragment(), View.OnClickListener, ShowRentersAdapter.OnClic
private var searchView: SearchView? = null
private var listSize = 0
+
+ private var renterNameWithTheirDues: Map? = null
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -103,6 +119,15 @@ class HomeFragment : Fragment(), View.OnClickListener, ShowRentersAdapter.OnClic
initListeners()
observeRevenueGenerated()
+ observeRenterDues()
+ }
+
+ private fun observeRenterDues() {
+
+ renterViewModel.renterNameWithTheirDues.observe(viewLifecycleOwner) { renterNameAndTheirDuesMap ->
+
+ renterNameWithTheirDues = renterNameAndTheirDuesMap
+ }
}
private fun getRvState() {
@@ -564,7 +589,8 @@ class HomeFragment : Fragment(), View.OnClickListener, ShowRentersAdapter.OnClic
.setOnMenuItemClickListener(this)
binding.houseRentersHomeToolBar.menu.findItem(R.id.menu_renter_revenue_custom_range)
.setOnMenuItemClickListener(this)
-
+ binding.houseRentersHomeToolBar.menu.findItem(R.id.menu_renter_dues)
+ .setOnMenuItemClickListener(this)
}
private var d1 = System.currentTimeMillis() - (30 * Constants.ONE_DAY_MILLISECONDS)
@@ -588,6 +614,7 @@ class HomeFragment : Fragment(), View.OnClickListener, ShowRentersAdapter.OnClic
true
}
+
R.id.menu_show_deleted_renters -> {
findNavController().navigate(R.id.action_homeFragment_to_deletedRentersFragment)
@@ -657,16 +684,47 @@ class HomeFragment : Fragment(), View.OnClickListener, ShowRentersAdapter.OnClic
R.id.menu_renter_revenue_all_time -> {
isRevenueObserveEnabled = true
- revenueTitle = "Revenue - All Time"
+ revenueTitle = getString(R.string.revenue_all_time)
renterViewModel.getRentersWithTheirAmountPaid()
true
}
+ R.id.menu_renter_dues -> {
+
+ showDuesInAlertDialogBox()
+
+ true
+ }
+
else -> false
}
}
+ private fun showDuesInAlertDialogBox() {
+
+ var totalDues = 0.0
+
+ val message = StringBuilder()
+
+ if (renterNameWithTheirDues != null && renterNameWithTheirDues!!.isNotEmpty()) {
+
+ renterNameWithTheirDues!!.forEach { entry ->
+ totalDues += abs(entry.value)
+ message.append("${entry.key} ===> ${abs(entry.value).format(2)}\n\n")
+ }
+ }
+
+ message.append("----------------------------------------\n\n")
+ message.append(" Total : ${totalDues.format(2)}")
+
+
+ showAlertDialogWithTitleAndMessage(
+ getString(R.string.renter_dues),
+ message.toString()
+ )
+ }
+
private fun showRevenueMessageByDateRange(
customDateRange: CustomDateRange,
millis: Pair
@@ -737,7 +795,7 @@ class HomeFragment : Fragment(), View.OnClickListener, ShowRentersAdapter.OnClic
MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(message)
- .setPositiveButton("Ok") { dialog, _ ->
+ .setPositiveButton(getString(R.string.ok)) { dialog, _ ->
dialog.dismiss()
}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/ShowPaymentMethodSelectorDialogFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/ShowPaymentMethodSelectorDialogFragment.kt
new file mode 100644
index 00000000..b3712d7c
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/ShowPaymentMethodSelectorDialogFragment.kt
@@ -0,0 +1,159 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.StaggeredGridLayoutManager
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.adapters.SelectPaymentMethodAdapter
+import com.rohitthebest.manageyourrenters.data.filter.ExpenseFilterDto
+import com.rohitthebest.manageyourrenters.database.model.PaymentMethod
+import com.rohitthebest.manageyourrenters.databinding.DialogPaymentMethodSelectorBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.ui.viewModels.PaymentMethodViewModel
+import com.rohitthebest.manageyourrenters.utils.convertJsonToObject
+import com.rohitthebest.manageyourrenters.utils.isValid
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class ShowPaymentMethodSelectorDialogFragment :
+ BottomSheetDialogFragment(R.layout.dialog_payment_method_selector),
+ SelectPaymentMethodAdapter.OnClickListener {
+
+ private var _binding: DialogPaymentMethodSelectorBinding? = null
+ private val binding get() = _binding!!
+
+ private val paymentMethodViewModel by viewModels()
+
+ private lateinit var selectPaymentMethodAdapter: SelectPaymentMethodAdapter
+
+ private var mListener: OnClickListener? = null
+
+ private var selectedPaymentMethods: List = emptyList()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ _binding = DialogPaymentMethodSelectorBinding.bind(view)
+
+ selectPaymentMethodAdapter = SelectPaymentMethodAdapter()
+ setUpRecyclerView()
+ getMessage()
+ getAllPaymentMethods()
+ initListeners()
+ }
+
+
+ private fun getMessage() {
+
+ if (!arguments?.isEmpty!!) {
+
+ arguments?.let { bundle ->
+
+ if (bundle.getString(Constants.EXPENSE_FILTER_KEY).isValid()) {
+
+ val expenseFilterDto = bundle.getString(Constants.EXPENSE_FILTER_KEY)!!
+ .convertJsonToObject(ExpenseFilterDto::class.java)!!
+
+ selectedPaymentMethods = expenseFilterDto.paymentMethods
+ getAllPaymentMethods()
+ }
+ }
+ }
+ }
+
+ private fun getAllPaymentMethods() {
+
+ paymentMethodViewModel.getAllPaymentMethods()
+ .observe(viewLifecycleOwner) { paymentMethods ->
+
+ if (selectedPaymentMethods.isNotEmpty()) {
+ paymentMethods.forEach { pm ->
+ if (selectedPaymentMethods.contains(pm.key)) {
+ pm.isSelected = true
+ }
+ }
+ }
+
+ selectPaymentMethodAdapter.submitList(paymentMethods)
+ }
+ }
+
+ private fun setUpRecyclerView() {
+
+ binding.paymentMethodsRV.apply {
+ setHasFixedSize(true)
+ adapter = selectPaymentMethodAdapter
+ layoutManager = StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL)
+ }
+
+ selectPaymentMethodAdapter.setOnClickListener(this)
+ }
+
+ override fun onItemClick(paymentMethod: PaymentMethod, position: Int) {
+
+ paymentMethod.isSelected = !paymentMethod.isSelected
+ selectPaymentMethodAdapter.notifyItemChanged(position)
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.menu.findItem(R.id.menu_item_filter_apply).apply {
+
+ setOnMenuItemClickListener {
+
+ if (mListener != null) {
+ mListener!!.onFilterApply(
+ selectPaymentMethodAdapter.currentList.filter { it.isSelected }
+ .map { it.key }
+ )
+ }
+
+ dismiss()
+
+ true
+ }
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_item_filter_clear).setOnMenuItemClickListener {
+
+ if (mListener != null) {
+ mListener!!.onFilterApply(emptyList())
+ }
+
+ dismiss()
+
+ true
+ }
+
+ }
+
+ fun setOnClickListener(listener: OnClickListener) {
+ mListener = listener
+ }
+
+ interface OnClickListener {
+
+ // method
+ fun onFilterApply(selectedPaymentMethods: List?)
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance(bundle: Bundle): ShowPaymentMethodSelectorDialogFragment {
+ val fragment = ShowPaymentMethodSelectorDialogFragment()
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+
+ _binding = null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/AddEditExpense.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/AddEditExpense.kt
index 6366429a..11f65528 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/AddEditExpense.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/AddEditExpense.kt
@@ -31,10 +31,11 @@ import com.rohitthebest.manageyourrenters.utils.Functions.Companion.getUid
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
import com.rohitthebest.manageyourrenters.utils.isTextValid
+import com.rohitthebest.manageyourrenters.utils.isValid
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import java.util.*
+import java.util.Calendar
private const val TAG = "AddEditExpense"
@@ -113,7 +114,7 @@ class AddEditExpense : Fragment(R.layout.fragment_add_expense), View.OnClickList
receivedExpenseKey = args.expenseKey!!
- if (receivedExpenseKey != "") {
+ if (receivedExpenseKey.isValid()) {
isMessageReceivedForEditing = true
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/DeepAnalyzeExpenseFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/DeepAnalyzeExpenseFragment.kt
index 71d55d8e..06025232 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/DeepAnalyzeExpenseFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/DeepAnalyzeExpenseFragment.kt
@@ -26,9 +26,16 @@ import com.rohitthebest.manageyourrenters.database.model.ExpenseCategory
import com.rohitthebest.manageyourrenters.databinding.FragmentDeepAnalyzeExpenseBinding
import com.rohitthebest.manageyourrenters.others.Constants
import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseCategoryViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseGraphDataViewModel
import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseViewModel
-import com.rohitthebest.manageyourrenters.utils.*
+import com.rohitthebest.manageyourrenters.utils.Functions
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.executeAfterDelay
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.loadAnyValueFromSharedPreference
+import com.rohitthebest.manageyourrenters.utils.show
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -46,6 +53,7 @@ class DeepAnalyzeExpenseFragment : Fragment(R.layout.fragment_deep_analyze_expen
private val expenseCategoryViewModel by viewModels()
private val expenseViewModel by viewModels()
+ private val expenseGraphDataViewModel by viewModels()
private lateinit var pie: Pie
@@ -79,6 +87,50 @@ class DeepAnalyzeExpenseFragment : Fragment(R.layout.fragment_deep_analyze_expen
getAllExpenseCategories()
initListeners()
+
+ observerExpenseGraphData()
+ }
+
+ private fun observerExpenseGraphData() {
+
+ expenseGraphDataViewModel.expenseGraphData.observe(viewLifecycleOwner) {
+
+ if (it != null && it.first.isNotEmpty()) {
+
+ val expenseCategoryNameAndTheirTotalList = it.first
+ val total = it.second
+
+ pie.title("Total expense amount: ${total.format(2)}")
+
+ val data = ArrayList()
+
+ expenseCategoryNameAndTheirTotalList.forEach { expenseCategoryAndTheirTotalExpenseAmounts ->
+
+ data.add(
+ ValueDataEntry(
+ expenseCategoryAndTheirTotalExpenseAmounts.categoryName,
+ expenseCategoryAndTheirTotalExpenseAmounts.totalAmount
+ )
+ )
+ }
+
+ lifecycleScope.launch {
+
+ delay(500)
+
+ if (data.isNotEmpty()) {
+
+ binding.chart.show()
+ pie.data(data)
+ } else {
+
+ binding.chart.hide()
+ showToast(requireContext(), getString(R.string.no_data_available))
+ }
+
+ }
+ }
+ }
}
private fun loadCustomDateRangeValueFromSharedPreference() {
@@ -243,12 +295,16 @@ class DeepAnalyzeExpenseFragment : Fragment(R.layout.fragment_deep_analyze_expen
CustomDateRange.THIS_MONTH -> binding.toolbar.subtitle =
getString(R.string.this_month)
+
CustomDateRange.THIS_WEEK -> binding.toolbar.subtitle =
getString(R.string.this_week)
+
CustomDateRange.PREVIOUS_MONTH -> binding.toolbar.subtitle =
getString(R.string.previous_month)
+
CustomDateRange.PREVIOUS_WEEK -> binding.toolbar.subtitle =
getString(R.string.previous_week)
+
else -> {}
}
initChartData()
@@ -380,146 +436,22 @@ class DeepAnalyzeExpenseFragment : Fragment(R.layout.fragment_deep_analyze_expen
val selectedCategories =
deepAnalyzeExpenseCategoryAdapter.currentList.filter { expenseCategory ->
-
expenseCategory.isSelected
- }
-
- getTotalExpenseAmount(selectedCategories)
-
- prepareChart(selectedCategories)
-
- }
-
- private fun prepareChart(selectedCategories: List) {
-
- if (selectedCategories.isNotEmpty()) {
-
- val data = ArrayList()
-
- selectedCategories.forEach { expenseCategory ->
-
- lifecycleScope.launch {
-
- if (!isDateRangeSelected) {
-
- // get the all time amount
-
- try {
- expenseViewModel.getExpenseAmountSumByExpenseCategoryKey(
- expenseCategory.key
- ).collect { amount ->
-
- data.add(
- ValueDataEntry(
- expenseCategory.categoryName,
- amount
- )
- )
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
-
- } else {
-
- // get the amount by the date range selected
-
- try {
- expenseViewModel.getExpenseAmountSumByExpenseCategoryByDateRange(
- expenseCategory.key,
- startDate,
- endDate + Constants.ONE_DAY_MILLISECONDS
- ).collect { amount ->
-
- data.add(
- ValueDataEntry(
- expenseCategory.categoryName,
- amount
- )
- )
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
-
- }
-
- }
- }
-
- lifecycleScope.launch {
-
- delay(500)
-
- if (data.isNotEmpty()) {
-
- binding.chart.show()
- pie.data(data)
- //binding.chart.setChart(pie)
- } else {
-
- binding.chart.hide()
- showToast(requireContext(), "No Data available!!!")
- }
-
- }
-
- }
-
- }
-
- private fun getTotalExpenseAmount(selectedCategories: List) {
-
- var total = 0.0
+ }.map { it.key }
if (!isDateRangeSelected) {
- selectedCategories.forEach { expenseCategory ->
-
- expenseViewModel.getTotalExpenseAmountByExpenseCategory(expenseCategory.key)
- .observe(viewLifecycleOwner) { amount ->
-
- try {
-
- total += amount
- } catch (e: NullPointerException) {
-
- e.printStackTrace()
- }
-
- }
- }
+ expenseGraphDataViewModel.getTotalExpenseAmountWithTheirExpenseCategoryNamesForSelectedExpenseCategories(
+ selectedCategories
+ )
} else {
- selectedCategories.forEach { expenseCategory ->
-
-
- expenseViewModel.getTotalExpenseAmountByCategoryKeyAndDateRange(
- expenseCategory.key,
- startDate,
- endDate + Constants.ONE_DAY_MILLISECONDS
- )
- .observe(viewLifecycleOwner) { amount ->
-
- try {
-
- total += amount
- } catch (e: Exception) {
-
- e.printStackTrace()
- }
- }
-
- }
-
- }
-
- lifecycleScope.launch {
- delay(500)
- pie.title("Total expense amount: $total")
-
+ expenseGraphDataViewModel.getTotalExpenseAmountWithTheirExpenseCategoryNamesForSelectedExpenseCategoriesByDateRange(
+ selectedCategories,
+ startDate,
+ endDate + Constants.ONE_DAY_MILLISECONDS
+ )
}
-
}
private var changeChartDataJob: Job? = null
@@ -530,7 +462,10 @@ class DeepAnalyzeExpenseFragment : Fragment(R.layout.fragment_deep_analyze_expen
if (getSelectedExpenseCategoryCount() <= 2) {
- showToast(requireContext(), "Minimum two categories has to be selected")
+ showToast(
+ requireContext(),
+ getString(R.string.minimum_two_categories_needs_to_be_selected)
+ )
return
}
}
@@ -551,19 +486,9 @@ class DeepAnalyzeExpenseFragment : Fragment(R.layout.fragment_deep_analyze_expen
binding.clearSelectionFAB.enable()
}
- try {
-
- if (changeChartDataJob != null && changeChartDataJob?.isActive == true) {
-
- changeChartDataJob?.cancel()
- }
- } catch (e: Exception) {
- e.printStackTrace()
- } finally {
-
- changeChartDataJob = lifecycleScope.launch {
+ changeChartDataJob = lifecycleScope.launch {
- delay(250)
+ changeChartDataJob.executeAfterDelay(250) {
initChartData()
}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ExpenseCategoryFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ExpenseCategoryFragment.kt
index 9ece2a7d..894dae6e 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ExpenseCategoryFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ExpenseCategoryFragment.kt
@@ -311,6 +311,11 @@ class ExpenseCategoryFragment : Fragment(R.layout.fragment_expense_category),
findNavController().navigate(R.id.action_expenseCategoryFragment_to_paymentMethodsFragment)
true
}
+ binding.toolbar.menu.findItem(R.id.menu_create_budget).setOnMenuItemClickListener {
+
+ findNavController().navigate(R.id.action_expenseCategoryFragment_to_budgetAndIncomeFragment)
+ true
+ }
}
private fun setNoExpenseCategoryMessageTvVisibility(isVisible: Boolean) {
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/GraphFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/GraphFragment.kt
index f72c9352..ce3f5d7a 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/GraphFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/GraphFragment.kt
@@ -5,6 +5,7 @@ import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
+import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -19,11 +20,15 @@ import com.anychart.enums.LegendLayout
import com.rohitthebest.manageyourrenters.R
import com.rohitthebest.manageyourrenters.data.CustomDateRange
import com.rohitthebest.manageyourrenters.data.ShowExpenseBottomSheetTagsEnum
+import com.rohitthebest.manageyourrenters.data.filter.ExpenseFilterDto
import com.rohitthebest.manageyourrenters.databinding.FragmentGraphBinding
+import com.rohitthebest.manageyourrenters.others.Constants
import com.rohitthebest.manageyourrenters.others.Constants.CUSTOM_DATE_RANGE_FOR_GRAPH_FRAGMENT_SHARED_PREF_KEY
import com.rohitthebest.manageyourrenters.others.Constants.CUSTOM_DATE_RANGE_FOR_GRAPH_FRAGMENT_SHARED_PREF_NAME
import com.rohitthebest.manageyourrenters.others.Constants.ONE_DAY_MILLISECONDS
+import com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.ShowPaymentMethodSelectorDialogFragment
import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseCategoryViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseGraphDataViewModel
import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseViewModel
import com.rohitthebest.manageyourrenters.utils.*
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.getUid
@@ -36,19 +41,25 @@ import kotlinx.coroutines.launch
private const val TAG = "GraphFragment"
@AndroidEntryPoint
-class GraphFragment : Fragment(R.layout.fragment_graph) {
+class GraphFragment : Fragment(R.layout.fragment_graph),
+ ShowPaymentMethodSelectorDialogFragment.OnClickListener {
private var _binding: FragmentGraphBinding? = null
private val binding get() = _binding!!
private val expenseCategoryViewModel by viewModels()
private val expenseViewModel by viewModels()
+ private val expenseGraphDataViewModel by viewModels()
private lateinit var pie: Pie
private var isAllTimeSelected = false
+ private var d1 = System.currentTimeMillis() - (ONE_DAY_MILLISECONDS * 30)
+ private var d2 = System.currentTimeMillis()
private var selectedCustomDateRangeMenu: CustomDateRange? = CustomDateRange.ALL_TIME
+ private var expenseFilterDto: ExpenseFilterDto? = null
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentGraphBinding.bind(view)
@@ -61,6 +72,60 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
handleDateRangeSelectionMenu(selectedCustomDateRangeMenu ?: CustomDateRange.ALL_TIME)
initListeners()
+
+ observeExpenseGraphData()
+ }
+
+ private fun observeExpenseGraphData() {
+
+ expenseGraphDataViewModel.expenseGraphData.observe(viewLifecycleOwner) {
+
+ if (it != null && it.first.isNotEmpty()) {
+
+ val expenseCategoryNameAndTheirTotalList = it.first
+ val total = it.second
+
+ pie.legend().title(getString(R.string.total_expense, total.format(3)))
+
+ val data = ArrayList()
+
+ expenseCategoryNameAndTheirTotalList.forEach { expenseCategoryAndTheirTotalExpenseAmounts ->
+
+ data.add(
+ ValueDataEntry(
+ expenseCategoryAndTheirTotalExpenseAmounts.categoryName,
+ expenseCategoryAndTheirTotalExpenseAmounts.totalAmount
+ )
+ )
+ }
+
+ lifecycleScope.launch {
+
+ delay(500)
+
+ Log.d(TAG, "getAllExpenseCategory: ${data.size}")
+
+ if (data.isNotEmpty()) {
+
+ binding.chart.show()
+ binding.noDataTV.hide()
+ pie.data(data)
+ } else {
+
+ handleUIForNoDataAvailable()
+ }
+ }
+ } else {
+ handleUIForNoDataAvailable()
+ }
+ }
+ }
+
+ private fun handleUIForNoDataAvailable() {
+
+ binding.chart.hide()
+ binding.noDataTV.show()
+ showToast(requireContext(), getString(R.string.no_data_available))
}
private fun loadCustomDateRangeValueFromSharedPreference() {
@@ -89,7 +154,7 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
binding.chart.setProgressBar(binding.progressBar)
pie.title().enabled(true)
- pie.title("All time")
+ pie.title(getString(R.string.all_time))
//pie.title("Expenses on each category")
pie.labels().position("outside")
@@ -107,9 +172,6 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
binding.chart.setChart(pie)
}
- private var d1 = System.currentTimeMillis() - (ONE_DAY_MILLISECONDS * 30)
- private var d2 = System.currentTimeMillis()
-
private fun initListeners() {
binding.toolbar.setNavigationOnClickListener {
@@ -118,74 +180,44 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
binding.toolbar.menu.findItem(R.id.menu_deep_analyze_expense).setOnMenuItemClickListener {
- expenseCategoryViewModel.getAllExpenseCategories().observe(viewLifecycleOwner) {
-
- if (it.size >= 2) {
-
- findNavController().navigate(R.id.action_graphFragment_to_deepAnalyzeExpenseFragment)
- } else {
-
- showToast(
- requireContext(),
- getString(R.string.depp_analyze_error_message),
- Toast.LENGTH_LONG
- )
- }
- }
-
+ handleCategoryCompareGraph()
true
}
binding.toolbar.menu.findItem(R.id.menu_monthly_graph).setOnMenuItemClickListener {
- expenseViewModel.getAllExpenses().observe(viewLifecycleOwner) {
-
- if (it.isNotEmpty()) {
- findNavController().navigate(R.id.action_graphFragment_to_monthlyGraphFragment)
- } else {
-
- showToast(requireContext(), getString(R.string.no_expense_added))
- }
- }
-
+ handleMonthlyGraphMenu()
true
}
- binding.toolbar.menu.findItem(R.id.menu_share_expense_graph_sc).setOnMenuItemClickListener {
-
- binding.toolbar.hide()
+ binding.toolbar.menu.findItem(R.id.menu_category_graph).setOnMenuItemClickListener {
- val bitmap = binding.root.loadBitmap()
-
- binding.toolbar.show()
+ handleCategoryGraph()
+ true
+ }
- lifecycleScope.launch {
- saveBitmapToCacheDirectoryAndShare(requireActivity(), bitmap)
- }
+ binding.toolbar.menu.findItem(R.id.menu_share_expense_graph_sc).setOnMenuItemClickListener {
+ handleShareExpenseGraphScreenshotMenu()
true
}
binding.toolbar.menu.findItem(R.id.menu_save_expense_graph_sc).setOnMenuItemClickListener {
- binding.toolbar.hide()
-
- val bitmap = binding.root.loadBitmap()
-
- binding.toolbar.show()
-
- bitmap.saveToStorage(requireContext(), "${getUid()}_expense_graph")
+ handleSaveExpenseGraphScreenshotMenu()
+ true
+ }
- showToast(requireContext(), "Screenshot saved to phone storage")
+ binding.toolbar.menu.findItem(R.id.menu_filter_expense_graph).setOnMenuItemClickListener {
+ handleFilterExpenseGraphMenu()
true
}
binding.dateRangeMenuBtn.setOnClickListener { view ->
showMenuForSelectingCustomTime(view)
-
}
binding.dateRangeCv.setOnClickListener {
@@ -196,12 +228,147 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
date1 = d1,
date2 = d2,
callingFragementTag = ShowExpenseBottomSheetTagsEnum.GRAPH_FRAGMENT,
- paymentMethodKey = null
+ paymentMethodKey = if (expenseFilterDto != null && !expenseFilterDto?.paymentMethods.isNullOrEmpty()) {
+ convertStringListToJSON(expenseFilterDto?.paymentMethods ?: emptyList())
+ } else {
+ null
+ }
)
findNavController().navigate(action)
}
}
+ private fun handleFilterExpenseGraphMenu() {
+
+ //showing Payment Method Selector Dialog
+
+ requireActivity().supportFragmentManager.let { fragmentManager ->
+
+ val bundle = Bundle()
+ bundle.putString(
+ Constants.EXPENSE_FILTER_KEY,
+ if (expenseFilterDto == null) "" else expenseFilterDto.convertToJsonString()
+ )
+
+ ShowPaymentMethodSelectorDialogFragment.newInstance(
+ bundle
+ ).apply {
+ show(fragmentManager, TAG)
+ }.setOnClickListener(this)
+ }
+
+ }
+
+ override fun onFilterApply(selectedPaymentMethods: List?) {
+
+ binding.toolbar.menu.findItem(R.id.menu_filter_expense_graph)
+ .apply {
+
+ if (selectedPaymentMethods.isNullOrEmpty()) {
+ this.icon =
+ ContextCompat.getDrawable(
+ requireContext(),
+ R.drawable.baseline_filter_list_24
+ )
+
+ expenseFilterDto = null
+
+ } else {
+ this.icon = ContextCompat.getDrawable(
+ requireContext(),
+ R.drawable.baseline_filter_list_colored_24
+ )
+
+ expenseFilterDto = ExpenseFilterDto()
+ expenseFilterDto!!.isPaymentMethodEnabled = true
+ expenseFilterDto!!.paymentMethods = selectedPaymentMethods
+ }
+ }
+
+ if (selectedCustomDateRangeMenu == CustomDateRange.ALL_TIME) {
+ // making this value to false, so it will enter the ALL_TIME case in handleDateRangeSelectionMenu function
+ isAllTimeSelected = false
+ }
+
+ handleDateRangeSelectionMenu(selectedCustomDateRangeMenu ?: CustomDateRange.ALL_TIME)
+ }
+
+ private fun handleSaveExpenseGraphScreenshotMenu() {
+
+ binding.toolbar.hide()
+
+ val bitmap = binding.root.loadBitmap()
+
+ binding.toolbar.show()
+
+ bitmap.saveToStorage(requireContext(), "${getUid()}_expense_graph")
+
+ showToast(requireContext(), getString(R.string.screenshot_saved_to_phone_storage))
+ }
+
+ private fun handleShareExpenseGraphScreenshotMenu() {
+
+ binding.toolbar.hide()
+
+ val bitmap = binding.root.loadBitmap()
+
+ binding.toolbar.show()
+
+ lifecycleScope.launch {
+
+ saveBitmapToCacheDirectoryAndShare(requireActivity(), bitmap)
+ }
+ }
+
+ private fun handleCategoryGraph() {
+
+ expenseViewModel.isAnyExpenseAdded().observe(viewLifecycleOwner) {
+
+ if (it) {
+ val action = GraphFragmentDirections.actionGraphFragmentToMonthlyGraphFragment(
+ true
+ )
+ findNavController().navigate(action)
+ } else {
+
+ showToast(requireContext(), getString(R.string.no_expense_added))
+ }
+ }
+
+ }
+
+ private fun handleMonthlyGraphMenu() {
+
+ expenseViewModel.isAnyExpenseAdded().observe(viewLifecycleOwner) {
+
+ if (it) {
+ findNavController().navigate(R.id.action_graphFragment_to_monthlyGraphFragment)
+ } else {
+
+ showToast(requireContext(), getString(R.string.no_expense_added))
+ }
+ }
+ }
+
+ private fun handleCategoryCompareGraph() {
+
+ expenseCategoryViewModel.getAllExpenseCategoriesByLimit(2).observe(viewLifecycleOwner) {
+
+ if (it.size >= 2) {
+
+ findNavController().navigate(R.id.action_graphFragment_to_deepAnalyzeExpenseFragment)
+ } else {
+
+ showToast(
+ requireContext(),
+ getString(R.string.depp_analyze_error_message),
+ Toast.LENGTH_LONG
+ )
+ }
+ }
+
+ }
+
private fun showMenuForSelectingCustomTime(view: View) {
Functions.showCustomDateRangeOptionMenu(
@@ -225,14 +392,16 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
if (!isAllTimeSelected) {
+ isAllTimeSelected = true
+
Log.d(
TAG,
- "handleDateRangeSelectionMenu: all time selected : $isAllTimeSelected"
+ "handleDateRangeSelectionMenu: paymentMethods: ${expenseFilterDto?.paymentMethods}"
)
- isAllTimeSelected = true
-
- getExpenseCategoryExpensesByAllTime()
+ expenseGraphDataViewModel.getTotalExpenseAmountsWithTheirExpenseCategoryNames(
+ expenseFilterDto?.paymentMethods ?: emptyList()
+ )
changeSelectionUI()
@@ -330,9 +499,11 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
if (!isAllTimeSelected) {
changeSelectionUI()
- getExpenseCategoryExpensesByDateRange(
- d1,
- d2
+
+ expenseGraphDataViewModel.getTotalExpenseAmountsWithTheirExpenseCategoryNamesByDateRange(
+ date1 = d1,
+ date2 = d2 + ONE_DAY_MILLISECONDS,
+ paymentMethodKeys = expenseFilterDto?.paymentMethods ?: emptyList()
)
}
}
@@ -342,7 +513,7 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
if (isAllTimeSelected) {
- binding.dateRangeTv.text = "All time"
+ binding.dateRangeTv.text = getString(R.string.all_time)
} else {
binding.dateRangeTv.text = "${
@@ -358,148 +529,6 @@ class GraphFragment : Fragment(R.layout.fragment_graph) {
}
}
- private fun getExpenseCategoryExpensesByDateRange(date1: Long?, date2: Long?) {
-
- if (!isAllTimeSelected) {
-
- Log.d(TAG, "getExpenseCategoryExpensesByDateRange: ")
-
- expenseViewModel.getTotalExpenseAmountByDateRange(
- date1!!,
- date2!! + ONE_DAY_MILLISECONDS
- )
- .observe(viewLifecycleOwner) { total ->
-
- pie.legend().title("Total expense : %.3f".format(total))
- }
-
-
- expenseCategoryViewModel.getAllExpenseCategories()
- .observe(viewLifecycleOwner) { expenseCategories ->
-
- if (expenseCategories.isNotEmpty()) {
-
- val data = ArrayList()
-
- expenseCategories.forEach { expenseCategory ->
-
- lifecycleScope.launch {
-
- try {
- expenseViewModel.getExpenseAmountSumByExpenseCategoryByDateRange(
- expenseCategory.key, date1, date2 + ONE_DAY_MILLISECONDS
- ).collect { amount ->
-
- Log.d(
- TAG,
- "getExpenseCategoryExpensesByDateRange: category : ${expenseCategory.categoryName} -> amount : $amount"
- )
-
- data.add(
- ValueDataEntry(
- expenseCategory.categoryName,
- amount
- )
- )
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- }
-
- lifecycleScope.launch {
-
- delay(500)
-
- Log.d(TAG, "getAllExpenseCategory: $data")
-
- if (data.isNotEmpty()) {
-
- binding.chart.show()
- pie.data(data)
- } else {
-
- binding.chart.hide()
- showToast(requireContext(), "No data found")
- }
- }
-
- }
- }
- }
-
- }
-
- private fun getExpenseCategoryExpensesByAllTime() {
-
- if (isAllTimeSelected) {
-
-
- Log.d(TAG, "getExpenseCategoryExpensesByAllTime: ")
-
- expenseViewModel.getTotalExpenseAmount().observe(viewLifecycleOwner) { total ->
-
- pie.legend().title("Total expense : $total")
- }
-
- expenseCategoryViewModel.getAllExpenseCategories()
- .observe(viewLifecycleOwner) { expenseCategories ->
-
- if (expenseCategories.isNotEmpty()) {
-
- val data = ArrayList()
-
- expenseCategories.forEach { expenseCategory ->
-
- lifecycleScope.launch {
-
- try {
- expenseViewModel.getExpenseAmountSumByExpenseCategoryKey(
- expenseCategory.key
- ).collect { amount ->
-
- Log.d(
- TAG,
- "getExpenseCategoryExpensesByAllTime: category : ${expenseCategory.categoryName} -> amount : $amount"
- )
-
- data.add(
- ValueDataEntry(
- expenseCategory.categoryName,
- amount
- )
- )
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- }
-
- lifecycleScope.launch {
-
- delay(500)
-
- Log.d(TAG, "getAllExpenseCategory: $data")
-
- if (data.isNotEmpty()) {
-
- binding.chart.show()
- pie.data(data)
- //binding.chart.setChart(pie)
- } else {
-
- binding.chart.hide()
- }
-
- }
-
- }
- }
- }
- }
-
override fun onDestroyView() {
super.onDestroyView()
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/MonthlyGraphFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/MonthlyGraphFragment.kt
index d128a89b..dc6d6b9d 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/MonthlyGraphFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/MonthlyGraphFragment.kt
@@ -2,6 +2,7 @@ package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense
import android.os.Bundle
import android.view.View
+import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
@@ -12,7 +13,10 @@ import com.anychart.charts.Cartesian
import com.anychart.enums.HoverMode
import com.anychart.enums.TooltipPositionMode
import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.database.model.ExpenseCategory
import com.rohitthebest.manageyourrenters.databinding.FragmentMonthlyGraphBinding
+import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseCategoryViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseGraphDataViewModel
import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseViewModel
import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
import com.rohitthebest.manageyourrenters.utils.setListToSpinner
@@ -28,26 +32,24 @@ class MonthlyGraphFragment : Fragment(R.layout.fragment_monthly_graph) {
private var _binding: FragmentMonthlyGraphBinding? = null
private val binding get() = _binding!!
+ private val expenseGraphDataViewModel by viewModels()
private val expenseViewModel by viewModels()
+ private val expenseCategoryViewModel by viewModels()
private lateinit var cartesian: Cartesian
private var selectedYear = 2020
- private var isRefreshEnabled = true
+ private lateinit var expenseCategories: List
+ private lateinit var selectedCategory: ExpenseCategory
+
+ private var isCategoryEnabled = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMonthlyGraphBinding.bind(view)
- observeExpenseOfEachMonth()
-
+ getMessage()
setUpChart()
- getStartAndEndYear()
-
- lifecycleScope.launch {
- delay(250)
- loadData()
- }
binding.toolbar.setNavigationOnClickListener {
@@ -55,38 +57,76 @@ class MonthlyGraphFragment : Fragment(R.layout.fragment_monthly_graph) {
}
}
- private fun loadData() {
- expenseViewModel.getExpensesOfAllMonthsOfYear(selectedYear)
+ private fun getMessage() {
+
+ if (!arguments?.isEmpty!!) {
+
+ val args = arguments?.let {
+
+ MonthlyGraphFragmentArgs.fromBundle(it)
+ }
+
+ isCategoryEnabled = args?.isCategoryExpenseEnabled ?: false
+ binding.mcvCategories.isVisible = isCategoryEnabled
+
+ if (isCategoryEnabled) {
+ observeAllExpenseCategories()
+ binding.toolbar.title = getString(R.string.monthly_category_expense_graph)
+ }
+ observeExpenseOfEachMonth()
+ getStartAndEndYear()
+ }
}
- private fun observeExpenseOfEachMonth() {
+ private fun observeAllExpenseCategories() {
- expenseViewModel.expenseOfEachMonth.observe(viewLifecycleOwner) { expensePerMonth ->
+ expenseCategoryViewModel.getAllExpenseCategories().observe(viewLifecycleOwner) {
+ expenseCategories = it
- if (isRefreshEnabled) {
- val monthList = resources.getStringArray(R.array.months_short).asList()
- var i = 0
+ if (it.isNotEmpty()) {
+ initCategorySpinner()
+ }
+ }
+ }
- val data = ArrayList()
+ private fun loadData() {
+ expenseGraphDataViewModel.getExpensesOfAllMonthsOfYear(selectedYear)
+ }
- expensePerMonth.forEach { expense ->
+ private fun observeExpenseOfEachMonth() {
- data.add(ValueDataEntry(monthList[i], expense))
- ++i
- }
+ expenseGraphDataViewModel.expenseOfEachMonth.observe(viewLifecycleOwner) { expensePerMonth ->
- lifecycleScope.launch {
+ val data = ArrayList()
- delay(500)
+ expensePerMonth.forEach { monthAndAmount ->
+ data.add(ValueDataEntry(monthAndAmount.first, monthAndAmount.second))
+ }
- cartesian.title("Monthly expense for the year : $selectedYear")
- cartesian.data(data)
+ lifecycleScope.launch {
+
+ delay(500)
+
+ if (isCategoryEnabled) {
+ cartesian.title(
+ getString(
+ R.string.monthly_expense_for_the_year_and_category,
+ selectedYear.toString(), selectedCategory.categoryName
+ )
+ )
+ } else {
+
+ cartesian.title(
+ getString(
+ R.string.monthly_expense_for_the_year,
+ selectedYear.toString()
+ )
+ )
}
- isRefreshEnabled = false
+ cartesian.data(data)
}
}
-
}
private fun setUpChart() {
@@ -105,8 +145,13 @@ class MonthlyGraphFragment : Fragment(R.layout.fragment_monthly_graph) {
cartesian.tooltip().positionMode(TooltipPositionMode.POINT)
cartesian.interactivity().hoverMode(HoverMode.BY_X)
- cartesian.xAxis(0).title("Month")
- cartesian.yAxis(0).title("Expense")
+ cartesian.xAxis(0).title(getString(R.string.month))
+ cartesian.yAxis(0).title(getString(R.string.expense))
+
+ cartesian.labels(true)
+ cartesian.labels().format("{%value}")
+ cartesian.labels().fontSize(10)
+ cartesian.labels().fontColor("#4D4C4C")
binding.chart.setChart(cartesian)
}
@@ -141,12 +186,44 @@ class MonthlyGraphFragment : Fragment(R.layout.fragment_monthly_graph) {
{ position ->
selectedYear = yearList[position]
- isRefreshEnabled = true
- loadData()
+ if (isCategoryEnabled) {
+ loadDataForSelectedCategory()
+ } else {
+ loadData()
+ }
}, {}
)
}
+ private fun initCategorySpinner() {
+
+ if (this::expenseCategories.isInitialized) {
+
+ binding.mcvCategories.isVisible = expenseCategories.isNotEmpty()
+
+ if (expenseCategories.isNotEmpty()) {
+
+ binding.categorySpinner.setListToSpinner(
+ requireContext(),
+ expenseCategories.map { it.categoryName },
+ { position ->
+
+ selectedCategory = expenseCategories[position]
+ loadDataForSelectedCategory()
+ }, {}
+ )
+ }
+ }
+ }
+
+ private fun loadDataForSelectedCategory() {
+ expenseGraphDataViewModel.getExpensesOfAllMonthsOfYearForSelectedCategory(
+ selectedYear,
+ selectedCategory.key
+ )
+ }
+
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ShowExpenseBottomSheetFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ShowExpenseBottomSheetFragment.kt
index da811002..a21ffd36 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ShowExpenseBottomSheetFragment.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/ShowExpenseBottomSheetFragment.kt
@@ -19,6 +19,7 @@ import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseCategoryViewModel
import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseViewModel
import com.rohitthebest.manageyourrenters.ui.viewModels.PaymentMethodViewModel
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.convertJSONToStringList
import com.rohitthebest.manageyourrenters.utils.isValid
import dagger.hilt.android.AndroidEntryPoint
@@ -117,10 +118,45 @@ class ShowExpenseBottomSheetFragment : BottomSheetDialogFragment(), ExpenseAdapt
ShowExpenseBottomSheetTagsEnum.PAYMENT_METHOD_FRAGMENT -> {
showInPaymentMethodFragment(args)
}
+
+ ShowExpenseBottomSheetTagsEnum.BUDGET_AND_INCOME_FRAGMENT -> {
+ showInBudgetAndIncomeOverviewFragment(args)
+ }
}
}
}
+ private fun showInBudgetAndIncomeOverviewFragment(args: ShowExpenseBottomSheetFragmentArgs) {
+
+ val budgetAndIncomeExpenseFilter = args.expenseFilterForBudgetAndIncome
+ val date1 = args.date1
+ val date2 = args.date2 + Constants.ONE_DAY_MILLISECONDS
+
+ if (budgetAndIncomeExpenseFilter != null) {
+
+ expenseViewModel.getExpenseByCategoryKeysAndDateRange(
+ budgetAndIncomeExpenseFilter.categoryKeys,
+ date1,
+ date2
+ ).observe(viewLifecycleOwner) { expenses ->
+
+ submitListToAdapter(
+ if (budgetAndIncomeExpenseFilter.paymentMethods.isEmpty()) {
+ expenses
+ } else {
+ expenseViewModel.applyFilterByPaymentMethods(
+ budgetAndIncomeExpenseFilter.paymentMethods, expenses
+ )
+ }
+ )
+ }
+ } else {
+ showToast(requireContext(), getString(R.string.something_went_wrong))
+ dismiss()
+ }
+
+ }
+
private fun showInPaymentMethodFragment(args: ShowExpenseBottomSheetFragmentArgs) {
paymentMethodKey = args.paymentMethodKey ?: ""
@@ -153,24 +189,37 @@ class ShowExpenseBottomSheetFragment : BottomSheetDialogFragment(), ExpenseAdapt
date1 = args.date1
date2 = args.date2 + Constants.ONE_DAY_MILLISECONDS
+ val paymentMethodKeys =
+ if (args.paymentMethodKey.isValid()) convertJSONToStringList(args.paymentMethodKey) else emptyList()
+
if (dateRangeEnum == CustomDateRange.ALL_TIME) {
- getAllExpenses()
+ getAllExpenses(paymentMethodKeys)
} else if (dateRangeEnum == CustomDateRange.CUSTOM_DATE_RANGE) {
- getExpensesByDateRange()
+ getExpensesByDateRange(paymentMethodKeys)
}
}
- private fun getExpensesByDateRange() {
+ private fun getExpensesByDateRange(paymentMethodKeys: List = emptyList()) {
expenseViewModel.getExpensesByDateRange(date1, date2)
.observe(viewLifecycleOwner) { expenses ->
- submitListToAdapter(expenses)
+ submitListToAdapter(
+ if (paymentMethodKeys.isEmpty()) expenses else expenseViewModel.applyFilterByPaymentMethods(
+ paymentMethodKeys,
+ expenses
+ )
+ )
}
}
- private fun getAllExpenses() {
+ private fun getAllExpenses(paymentMethodKeys: List = emptyList()) {
expenseViewModel.getAllExpenses().observe(viewLifecycleOwner) { expenses ->
- submitListToAdapter(expenses)
+ submitListToAdapter(
+ if (paymentMethodKeys.isEmpty()) expenses else expenseViewModel.applyFilterByPaymentMethods(
+ paymentMethodKeys,
+ expenses
+ )
+ )
}
}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddBudgetFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddBudgetFragment.kt
new file mode 100644
index 00000000..fe604ff0
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddBudgetFragment.kt
@@ -0,0 +1,611 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.PopupMenu
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome.SetBudgetExpenseCategoryAdapter
+import com.rohitthebest.manageyourrenters.data.CustomDateRange
+import com.rohitthebest.manageyourrenters.data.ShowExpenseBottomSheetTagsEnum
+import com.rohitthebest.manageyourrenters.data.filter.BudgetAndIncomeExpenseFilter
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.databinding.FragmentAddBudgetBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.ui.fragments.MonthAndYearPickerDialog
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showNoInternetMessage
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.convertStringListToJSON
+import com.rohitthebest.manageyourrenters.utils.convertToJsonString
+import com.rohitthebest.manageyourrenters.utils.executeAfterDelay
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.isValid
+import com.rohitthebest.manageyourrenters.utils.onTextChanged
+import com.rohitthebest.manageyourrenters.utils.onTextSubmit
+import com.rohitthebest.manageyourrenters.utils.show
+import com.rohitthebest.manageyourrenters.utils.showAlertDialogForDeletion
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "AddBudgetFragment"
+
+@AndroidEntryPoint
+class AddBudgetFragment : Fragment(R.layout.fragment_add_budget),
+ SetBudgetExpenseCategoryAdapter.OnClickListener,
+ AddBudgetLimitBottomSheetFragment.OnBottomSheetDismissListener,
+ MonthAndYearPickerDialog.OnMonthAndYearDialogDismissListener,
+ ChooseMonthAndYearBottomSheetFragment.OnBottomSheetDismissListener {
+
+ private var _binding: FragmentAddBudgetBinding? = null
+ private val binding get() = _binding!!
+
+ private val budgetViewModel by viewModels()
+
+ private var selectedMonth: Int = 0
+ private var selectedYear: Int = 0
+ private var monthList: List = emptyList()
+
+ private lateinit var setBudgetExpenseCategoryAdapter: SetBudgetExpenseCategoryAdapter
+ private var adapterPosition = 0
+ private var searchView: SearchView? = null
+
+ private var oldestYearWhenBudgetWasSaved = 2000
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentAddBudgetBinding.bind(view)
+
+ monthList = resources.getStringArray(R.array.months).toList()
+ setBudgetExpenseCategoryAdapter = SetBudgetExpenseCategoryAdapter()
+
+ binding.progressBar.show()
+
+ getMessage()
+ initListeners()
+ setUpRecyclerView()
+ observerBudgetList()
+ }
+
+ private fun observerBudgetList() {
+
+ budgetViewModel.allExpenseCategoryAsBudgets.observe(viewLifecycleOwner) { budgets ->
+ setUpSearchViewMenu(budgets)
+ getTotalBudget()
+ binding.progressBar.hide()
+ }
+ }
+
+ private fun setUpRecyclerView() {
+
+ binding.setBudgetRV.apply {
+
+ setHasFixedSize(true)
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = setBudgetExpenseCategoryAdapter
+ }
+ setBudgetExpenseCategoryAdapter.setOnClickListener(this)
+
+ }
+
+ override fun onItemClicked(expenseCategoryKey: String, isBudgetLimitAdded: Boolean) {
+
+ if (!isBudgetLimitAdded) {
+ showToast(requireContext(), getString(R.string.please_add_limit_for_this_category))
+ return
+ }
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ selectedMonth, selectedYear
+ )
+
+ val action =
+ AddBudgetFragmentDirections.actionAddBudgetFragmentToShowExpenseBottomSheetFragment(
+ null,
+ CustomDateRange.CUSTOM_DATE_RANGE,
+ datePairForExpense.first,
+ datePairForExpense.second,
+ ShowExpenseBottomSheetTagsEnum.BUDGET_AND_INCOME_FRAGMENT,
+ BudgetAndIncomeExpenseFilter(listOf(expenseCategoryKey), emptyList())
+ )
+
+ findNavController().navigate(action)
+ }
+
+ override fun onAddBudgetClicked(budget: Budget, position: Int) {
+
+ adapterPosition = position
+ val bundle = Bundle()
+ bundle.putBoolean(Constants.IS_FOR_EDIT, false)
+ bundle.putString(Constants.BUDGET, budget.convertToJsonString())
+
+ requireActivity().supportFragmentManager.let { fm ->
+
+ AddBudgetLimitBottomSheetFragment.newInstance(bundle)
+ .apply {
+ show(fm, TAG)
+ }.setOnBottomSheetDismissListener(this)
+ }
+ }
+
+ override fun onBottomSheetDismissed(isBudgetLimitAdded: Boolean) {
+
+ if (isBudgetLimitAdded) {
+
+ getAllExpenseCategoriesAsBudget()
+
+ setBudgetExpenseCategoryAdapter.notifyItemChanged(adapterPosition)
+ }
+ }
+
+
+ override fun onBudgetMenuBtnClicked(budget: Budget, view: View, position: Int) {
+
+ adapterPosition = position
+
+ val popupMenu = PopupMenu(requireContext(), view)
+ popupMenu.menuInflater.inflate(R.menu.menu_add_budget_item, popupMenu.menu)
+
+ popupMenu.show()
+
+ popupMenu.setOnMenuItemClickListener {
+
+ return@setOnMenuItemClickListener when (it.itemId) {
+
+ R.id.menu_ab_edit -> {
+
+ handleEditBudgetMenu(budget)
+ true
+ }
+
+ R.id.menu_ab_remove_limit -> {
+
+ handleRemoveBudgetLimitMenu(budget)
+ true
+ }
+
+ else -> false
+ }
+
+ }
+ }
+
+ override fun onBudgetSyncBtnClicked(budget: Budget, position: Int) {
+
+ if (isInternetAvailable(requireContext())) {
+
+ if (!budget.isSynced) {
+
+ budgetViewModel.insertBudget(budget)
+ setBudgetExpenseCategoryAdapter.notifyItemChanged(position)
+ }
+
+ } else {
+ showNoInternetMessage(requireContext())
+ }
+ }
+
+ private fun handleRemoveBudgetLimitMenu(budget: Budget) {
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(getString(R.string.are_you_sure))
+ .setMessage(getString(R.string.remove_budget_warning))
+ .setPositiveButton(getString(R.string.ok)) { dialog, _ ->
+ if (requireContext().isInternetAvailable()) {
+ budgetViewModel.deleteBudget(budget)
+ getAllExpenseCategoriesAsBudget()
+ setBudgetExpenseCategoryAdapter.notifyItemChanged(adapterPosition)
+ } else {
+ showNoInternetMessage(requireContext())
+ }
+ dialog.dismiss()
+ }
+ .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() }
+ .create()
+ .show()
+
+ }
+
+ private fun handleEditBudgetMenu(budget: Budget) {
+
+ val bundle = Bundle()
+ bundle.putBoolean(Constants.IS_FOR_EDIT, true)
+ bundle.putString(Constants.DOCUMENT_KEY, budget.key)
+
+ requireActivity().supportFragmentManager.let { fm ->
+
+ AddBudgetLimitBottomSheetFragment.newInstance(bundle)
+ .apply {
+ show(fm, TAG)
+ }.setOnBottomSheetDismissListener(this)
+ }
+ }
+
+ private fun getMessage() {
+
+ try {
+
+ if (!arguments?.isEmpty!!) {
+
+ val args = arguments?.let {
+ AddBudgetFragmentArgs.fromBundle(it)
+ }
+
+ selectedMonth = args?.monthMessage ?: WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = args?.yearMessage ?: WorkingWithDateAndTime.getCurrentYear()
+ } else {
+ selectedMonth = WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = WorkingWithDateAndTime.getCurrentYear()
+ }
+
+ lifecycleScope.launch {
+ delay(300)
+ initUI()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+
+ selectedMonth = WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = WorkingWithDateAndTime.getCurrentYear()
+ lifecycleScope.launch {
+ delay(300)
+ initUI()
+ }
+ }
+
+ }
+
+ private fun getAllExpenseCategoriesAsBudget() {
+
+ budgetViewModel.getAllExpenseCategoryAsBudget(selectedMonth, selectedYear)
+ }
+
+ private var searchTextDelayJob: Job? = null
+ private fun setUpSearchViewMenu(budgets: List) {
+
+ searchView =
+ binding.toolbar.menu.findItem(R.id.menu_search_budget_list).actionView as SearchView
+
+ searchView?.let { sv ->
+
+ searchBudget(sv.query.toString(), budgets)
+
+ sv.onTextSubmit { query -> searchBudget(query ?: "", budgets) }
+ sv.onTextChanged { query ->
+
+ searchTextDelayJob = lifecycleScope.launch {
+ searchTextDelayJob.executeAfterDelay {
+ searchBudget(query ?: "", budgets)
+ }
+ }
+ }
+ }
+ }
+
+ private fun searchBudget(query: String, budgets: List) {
+
+ if (!query.isValid()) {
+ setBudgetExpenseCategoryAdapter.submitList(budgets)
+
+ showNoBudgetAddedTV(
+ budgets.isEmpty(),
+ getString(R.string.no_expense_category_added_for_budget_message)
+ )
+
+ } else {
+
+ val filteredList = budgets.filter { budget ->
+
+ budget.categoryName.lowercase()
+ .contains(query.trim().lowercase())
+ }
+
+ showNoBudgetAddedTV(
+ filteredList.isEmpty(),
+ getString(R.string.no_matching_results_found_message)
+ )
+
+ setBudgetExpenseCategoryAdapter.submitList(filteredList)
+ }
+ }
+
+ private fun showNoBudgetAddedTV(
+ isVisible: Boolean,
+ noExpenseCategoryAddedForBudgetMessage: String = ""
+ ) {
+
+ binding.noBudgetLimitAddedTV.text = noExpenseCategoryAddedForBudgetMessage
+ binding.noBudgetLimitAddedTV.isVisible = isVisible
+ binding.setBudgetRV.isVisible = !isVisible
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.nextMonthBtn.setOnClickListener {
+ handleNextDateButton()
+ }
+ binding.previousMonthBtn.setOnClickListener {
+ handlePreviousDateButton()
+ }
+ binding.monthMCV.setOnClickListener {
+ handleMonthAndYearSelection()
+ }
+ binding.toolbar.menu.findItem(R.id.copy_previous_months_budget).setOnMenuItemClickListener {
+
+ handleCopyPreviousMonthBudgetMenu()
+ true
+ }
+ binding.toolbar.menu.findItem(R.id.delete_all_budget_limit).setOnMenuItemClickListener {
+ handleDeleteAllBudgetLimitMenu()
+ true
+ }
+
+ }
+
+ private fun handleDeleteAllBudgetLimitMenu() {
+
+ showAlertDialogForDeletion(
+ context = requireContext(),
+ message = getString(R.string.all_the_budget_limits_for_this_month_will_be_reset),
+ positiveButtonListener = {
+
+ if (isInternetAvailable(requireContext())) {
+ budgetViewModel.deleteAllBudgetsByMonthAndYear(selectedMonth, selectedYear)
+ getAllExpenseCategoriesAsBudget()
+ } else {
+ showNoInternetMessage(requireContext())
+ }
+
+ it.dismiss()
+ },
+ negativeButtonListener = {
+ it.dismiss()
+ }
+ )
+ }
+
+ private var isBudgetAddedForSelectedMonth = false
+ private fun handleCopyPreviousMonthBudgetMenu() {
+
+ var isRefreshEnabled = true
+
+ budgetViewModel.getAllBudgetMonthAndYearForWhichBudgetIsAdded()
+ .observe(viewLifecycleOwner) { monthYearStringList ->
+
+ if (isRefreshEnabled) {
+
+ Log.d(TAG, "initListeners: monthYearString: $monthYearStringList")
+
+ val monthYearStringForSelectedMonthAndYear =
+ WorkingWithDateAndTime.getMonthAndYearString(
+ selectedMonth,
+ selectedYear
+ )
+
+ val monthYearStringListAfterRemovingSelectedMonth =
+ ArrayList(monthYearStringList)
+
+ monthYearStringListAfterRemovingSelectedMonth.remove(
+ monthYearStringForSelectedMonthAndYear
+ )
+
+ if (monthYearStringListAfterRemovingSelectedMonth.isNotEmpty()) {
+
+ budgetViewModel.isAnyBudgetAddedForThisMonthAndYear(
+ monthYearStringForSelectedMonthAndYear
+ ).observe(viewLifecycleOwner) { budgetKeys ->
+
+ if (isRefreshEnabled) {
+ if (budgetKeys.isNotEmpty()) {
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(getString(R.string.are_you_sure))
+ .setMessage(getString(R.string.replace_the_current_budget_limit_for_this_month))
+ .setPositiveButton(getString(R.string.ok)) { dialog, _ ->
+
+ isBudgetAddedForSelectedMonth = true
+ showPreviousMonthAndYearListForWhichBudgetIsAdded(
+ monthYearStringListAfterRemovingSelectedMonth
+ )
+ dialog.dismiss()
+ }
+ .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
+ dialog.dismiss()
+ }
+ .create()
+ .show()
+
+ } else {
+
+ isBudgetAddedForSelectedMonth = false
+ showPreviousMonthAndYearListForWhichBudgetIsAdded(
+ monthYearStringList
+ )
+ }
+ }
+ isRefreshEnabled = false
+ }
+
+ } else {
+
+ showToast(
+ requireContext(),
+ getString(R.string.no_budget_limit_added_yet_for_any_month)
+ )
+ }
+
+ }
+ }
+
+ }
+
+ private fun showPreviousMonthAndYearListForWhichBudgetIsAdded(monthYearStringList: List?) {
+
+ Log.d(TAG, "showPreviousMonthAndYearListForWhichBudgetIsAdded: monthYearString")
+
+ val bundle = Bundle()
+ bundle.putString(
+ Constants.COPY_BUDGET_MONTH_AND_YEAR_KEY,
+ convertStringListToJSON(monthYearStringList ?: listOf(""))
+ )
+
+ requireActivity().supportFragmentManager.let { fm ->
+
+ ChooseMonthAndYearBottomSheetFragment.newInstance(bundle)
+ .apply {
+ show(fm, TAG)
+ }.setOnBottomSheetDismissListener(this)
+ }
+ }
+
+ override fun onMonthAndYearSelectedForCopyingBudget(
+ isMonthAndYearSelected: Boolean,
+ selectedMonthYearString: String // this is the month from which user want to copy/duplicate the budget
+ ) {
+
+ budgetViewModel.duplicateBudgetOfPreviouslyAddedBudgetMonth(
+ isBudgetAddedForSelectedMonth,
+ Pair(selectedMonth, selectedYear),
+ selectedMonthYearString
+ )
+
+ lifecycleScope.launch {
+ binding.progressBar.show()
+
+ delay(200)
+
+ getAllExpenseCategoriesAsBudget()
+
+ }
+ }
+
+
+ private fun handleMonthAndYearSelection() {
+
+ val bundle = Bundle()
+ bundle.putInt(Constants.MONTH_YEAR_PICKER_MONTH_KEY, selectedMonth)
+ bundle.putInt(Constants.MONTH_YEAR_PICKER_YEAR_KEY, selectedYear)
+ bundle.putInt(
+ Constants.MONTH_YEAR_PICKER_MIN_YEAR_KEY,
+ oldestYearWhenBudgetWasSaved - 4
+ )
+ bundle.putInt(
+ Constants.MONTH_YEAR_PICKER_MAX_YEAR_KEY,
+ WorkingWithDateAndTime.getCurrentYear()
+ )
+
+ requireActivity().supportFragmentManager.let { fm ->
+ MonthAndYearPickerDialog.newInstance(
+ bundle
+ ).apply {
+ show(fm, TAG)
+ }
+ }.setOnMonthAndYearDialogDismissListener(this)
+ }
+
+ override fun onMonthAndYearDialogDismissed(
+ isMonthAndYearSelected: Boolean,
+ selectedMonth: Int,
+ selectedYear: Int
+ ) {
+
+ if (isMonthAndYearSelected) {
+
+ this.selectedMonth = selectedMonth
+ this.selectedYear = selectedYear
+ handleUiAfterDateChange()
+ }
+ }
+
+
+ private fun initUI() {
+
+ binding.monthAndYearTV.text =
+ getString(R.string.month_and_year, monthList[selectedMonth], selectedYear.toString())
+
+ handleUiAfterDateChange()
+
+ budgetViewModel.getTheOldestSavedBudgetYear().observe(viewLifecycleOwner) { year ->
+ try {
+ if (year != null) {
+ oldestYearWhenBudgetWasSaved = year
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun handlePreviousDateButton() {
+
+ if (selectedMonth == 0) {
+ selectedYear -= 1
+ }
+
+ selectedMonth = WorkingWithDateAndTime.getPreviousMonth(selectedMonth)
+
+ handleUiAfterDateChange()
+ }
+
+ private fun handleNextDateButton() {
+
+ if (selectedMonth == 11) {
+ selectedYear += 1
+ }
+
+ selectedMonth = WorkingWithDateAndTime.getNextMonth(selectedMonth)
+ handleUiAfterDateChange()
+ }
+
+ private fun handleUiAfterDateChange() {
+
+ binding.monthAndYearTV.text =
+ getString(R.string.month_and_year, monthList[selectedMonth], selectedYear.toString())
+
+ getAllExpenseCategoriesAsBudget()
+ try {
+ binding.setBudgetRV.scrollToPosition(0)
+ } catch (_: Exception) {
+ }
+
+ getTotalBudget()
+ }
+
+ private fun getTotalBudget() {
+
+ budgetViewModel.getTotalBudgetByMonthAndYear(selectedMonth, selectedYear)
+ .observe(viewLifecycleOwner) { totalBudget ->
+ try {
+ binding.toolbar.subtitle =
+ getString(R.string.total_with_arg, totalBudget.format(2))
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ binding.toolbar.subtitle =
+ getString(R.string.total_with_arg, getString(R.string._0_0))
+ }
+ }
+ }
+
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddBudgetLimitBottomSheetFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddBudgetLimitBottomSheetFragment.kt
new file mode 100644
index 00000000..81209030
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddBudgetLimitBottomSheetFragment.kt
@@ -0,0 +1,269 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.text.InputFilter
+import android.text.InputType
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.viewModels
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.databinding.EditTextBottomSheetLayoutBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.generateKey
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.getUid
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.convertJsonToObject
+import com.rohitthebest.manageyourrenters.utils.isNotValid
+import com.rohitthebest.manageyourrenters.utils.isTextValid
+import com.rohitthebest.manageyourrenters.utils.isValid
+import com.rohitthebest.manageyourrenters.utils.onTextChangedListener
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class AddBudgetLimitBottomSheetFragment : BottomSheetDialogFragment() {
+
+ private var _binding: EditTextBottomSheetLayoutBinding? = null
+ private val binding get() = _binding!!
+
+ private val budgetViewModel by viewModels()
+ private var receivedBudgetKey = ""
+ private lateinit var receivedBudget: Budget
+
+ private var isForEdit = false
+ private var mListener: OnBottomSheetDismissListener? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ return inflater.inflate(R.layout.edit_text_bottom_sheet_layout, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ _binding = EditTextBottomSheetLayoutBinding.bind(view)
+
+ binding.toolbar.title = getString(R.string.add_budget_limit)
+ setUpEditText()
+
+ getMessage()
+ initListeners()
+ textWatchers()
+ }
+
+ private fun setUpEditText() {
+
+ binding.editText.counterMaxLength = 12
+ binding.editText.hint = getString(R.string.enter_budget_limit)
+
+ binding.insideEditText.apply {
+
+ inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
+
+ val maxLengthFilter: Array = arrayOf(InputFilter.LengthFilter(12))
+ filters = maxLengthFilter
+
+ hint = getString(R.string.enter_budget_limit)
+ }
+
+ binding.insideEditText.requestFocus()
+ Functions.showKeyboard(requireActivity(), binding.insideEditText)
+
+ }
+
+ private fun getMessage() {
+
+ if (!arguments?.isEmpty!!) {
+
+ arguments?.let { bundle ->
+
+ try {
+
+ isForEdit = bundle.getBoolean(Constants.IS_FOR_EDIT, false)
+
+ if (isForEdit) {
+
+ receivedBudgetKey = bundle.getString(Constants.DOCUMENT_KEY, "")
+ if (receivedBudgetKey.isNotValid()) {
+ requireContext().showToast(getString(R.string.something_went_wrong))
+ dismiss()
+ } else {
+
+ getBudgetByKey()
+ }
+ } else {
+
+ try {
+ receivedBudget = bundle.getString(Constants.BUDGET, "")
+ .convertJsonToObject(Budget::class.java)!!
+ receivedBudgetKey = receivedBudget.key
+ } catch (e: NullPointerException) {
+
+ requireContext().showToast(getString(R.string.something_went_wrong))
+ dismiss()
+ }
+
+ }
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ requireContext().showToast(getString(R.string.something_went_wrong))
+ dismiss()
+ }
+ }
+ }
+
+ }
+
+ private fun getBudgetByKey() {
+
+ budgetViewModel.getBudgetByKey(receivedBudgetKey).observe(viewLifecycleOwner) { budget ->
+
+ receivedBudget = budget
+ updateUI()
+ }
+ }
+
+ private fun updateUI() {
+
+ if (this::receivedBudget.isInitialized) {
+ binding.editText.editText?.setText(receivedBudget.budgetLimit.toString())
+ }
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.setNavigationOnClickListener {
+ Functions.hideKeyBoard(requireActivity())
+ dismiss()
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_save_btn).setOnMenuItemClickListener {
+
+ if (isFormValid()) {
+
+ if (isForEdit) {
+
+ updateBudgetLimit()
+ } else {
+ insertBudgetLimit()
+ }
+ }
+
+ true
+ }
+ }
+
+ private fun updateBudgetLimit() {
+
+ val budgetBefore = receivedBudget.copy()
+
+ receivedBudget.apply {
+
+ this.budgetLimit = binding.insideEditText.text.toString().trim().toDouble()
+ this.modified = System.currentTimeMillis()
+ }
+
+ budgetViewModel.updateBudget(budgetBefore, receivedBudget)
+
+ if (mListener != null) {
+ mListener!!.onBottomSheetDismissed(
+ true
+ )
+ }
+
+ dismiss()
+ }
+
+ private fun insertBudgetLimit() {
+
+ receivedBudget.apply {
+
+ this.uid = getUid()!!
+ this.key = generateKey("_${this.uid}")
+ this.modified = System.currentTimeMillis()
+ this.created = System.currentTimeMillis()
+ this.budgetLimit = binding.insideEditText.text.toString().trim().toDouble()
+ }
+
+ budgetViewModel.insertBudget(receivedBudget)
+
+ if (mListener != null) {
+ mListener!!.onBottomSheetDismissed(true)
+ }
+
+ dismiss()
+ }
+
+ private fun isFormValid(): Boolean {
+
+ if (!binding.editText.editText?.isTextValid()!!) {
+ binding.editText.error = Constants.EDIT_TEXT_EMPTY_MESSAGE
+ return false
+ }
+
+ if (binding.insideEditText.text.toString().toDouble() <= 0.0) {
+
+ binding.editText.error =
+ getString(R.string.budget_limit_should_be_grater_than_0)
+ return false
+ }
+
+ return binding.editText.error == null
+ }
+
+ private fun textWatchers() {
+
+ binding.insideEditText.onTextChangedListener { s ->
+
+ if (!s.toString().isValid()) {
+
+ binding.editText.error = Constants.EDIT_TEXT_EMPTY_MESSAGE
+ } else {
+
+ if (s?.toString()?.trim()?.toDouble()!! <= 0.0) {
+ binding.editText.error =
+ getString(R.string.budget_limit_should_be_grater_than_0)
+ } else {
+ binding.editText.error = null
+ }
+ }
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance(bundle: Bundle): AddBudgetLimitBottomSheetFragment {
+ val fragment = AddBudgetLimitBottomSheetFragment()
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+
+ interface OnBottomSheetDismissListener {
+
+ fun onBottomSheetDismissed(
+ isBudgetLimitAdded: Boolean
+ )
+ }
+
+ fun setOnBottomSheetDismissListener(listener: OnBottomSheetDismissListener) {
+
+ mListener = listener
+ }
+
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+
+ _binding = null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddIncomeBottomSheetFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddIncomeBottomSheetFragment.kt
new file mode 100644
index 00000000..44ce06a2
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/AddIncomeBottomSheetFragment.kt
@@ -0,0 +1,364 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.StaggeredGridLayoutManager
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.adapters.SelectPaymentMethodAdapter
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.database.model.PaymentMethod
+import com.rohitthebest.manageyourrenters.databinding.FragmentAddIncomeBootmsheetBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.others.Constants.EDIT_TEXT_EMPTY_MESSAGE
+import com.rohitthebest.manageyourrenters.ui.fragments.AddEditPaymentMethodBottomSheetFragment
+import com.rohitthebest.manageyourrenters.ui.viewModels.IncomeViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.PaymentMethodViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.isTextValid
+import com.rohitthebest.manageyourrenters.utils.isValid
+import com.rohitthebest.manageyourrenters.utils.onTextChangedListener
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "AddIncomeBottomSheetFra"
+
+@AndroidEntryPoint
+class AddIncomeBottomSheetFragment : BottomSheetDialogFragment(),
+ SelectPaymentMethodAdapter.OnClickListener {
+
+ private var _binding: FragmentAddIncomeBootmsheetBinding? = null
+ private val binding get() = _binding!!
+
+ private val incomeViewModel by viewModels()
+ private val paymentMethodViewModel by viewModels()
+
+ private var isForEdit = false
+ private var receivedMonth: Int = 0
+ private var receivedYear: Int = 0
+ private var receivedIncomeKey = ""
+ private lateinit var receivedIncome: Income
+
+ private var mListener: OnIncomeBottomSheetDismissListener? = null
+ private lateinit var selectPaymentMethodAdapter: SelectPaymentMethodAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View? {
+
+ return inflater.inflate(R.layout.fragment_add_income_bootmsheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentAddIncomeBootmsheetBinding.bind(view)
+
+ selectPaymentMethodAdapter = SelectPaymentMethodAdapter()
+
+ getMessage()
+ initListener()
+ textWatchers()
+ setUpSourceAutoTextView()
+ setUpPaymentMethodRecyclerView()
+ }
+
+ private fun setUpPaymentMethodRecyclerView() {
+
+ binding.linkPaymentMethodsRV.apply {
+
+ adapter = selectPaymentMethodAdapter
+ layoutManager = StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL)
+ setHasFixedSize(true)
+ }
+
+ selectPaymentMethodAdapter.setOnClickListener(this)
+ }
+
+ override fun onItemClick(paymentMethod: PaymentMethod, position: Int) {
+
+ if (paymentMethod.key != Constants.ADD_PAYMENT_METHOD_KEY) {
+
+ paymentMethod.isSelected = !paymentMethod.isSelected
+ selectPaymentMethodAdapter.notifyItemChanged(position)
+ } else {
+
+ requireActivity().supportFragmentManager.let {
+
+ AddEditPaymentMethodBottomSheetFragment.newInstance(Bundle())
+ .apply {
+ show(it, TAG)
+ }
+ }
+ }
+ }
+
+ private fun setUpSourceAutoTextView() {
+
+ incomeViewModel.getAllIncomeSources().observe(viewLifecycleOwner) { sources ->
+
+ val adapter = ArrayAdapter(
+ requireContext(),
+ android.R.layout.simple_list_item_1,
+ sources
+ )
+
+ binding.insideEditTextSource.setAdapter(adapter)
+ }
+ }
+
+ private fun getMessage() {
+
+ if (!arguments?.isEmpty!!) {
+
+ arguments?.let { bundle ->
+
+ try {
+
+ isForEdit = bundle.getBoolean(Constants.IS_FOR_EDIT, false)
+
+ if (isForEdit) {
+
+ receivedIncomeKey = bundle.getString(Constants.DOCUMENT_KEY, "")
+ if (receivedIncomeKey.isValid()) {
+
+ getIncomeByKey()
+ } else {
+ requireContext().showToast(getString(R.string.something_went_wrong))
+ dismiss()
+ }
+
+ binding.toolbar.title = getString(R.string.edit_income)
+
+ } else {
+
+ receivedMonth = bundle.getInt(Constants.INCOME_MONTH_KEY, -1)
+ receivedYear = bundle.getInt(Constants.INCOME_YEAR_KEY, -1)
+
+ if (receivedMonth == -1 || receivedYear == -1) {
+
+ showToast(
+ requireContext(),
+ getString(R.string.no_month_or_year_received_for_which_income_needs_to_be_added)
+ )
+ dismiss()
+ }
+ }
+
+ lifecycleScope.launch {
+
+ delay(300)
+ getAllPaymentMethods()
+ }
+
+
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ requireContext().showToast(getString(R.string.something_went_wrong))
+ dismiss()
+ }
+ }
+ }
+
+ }
+
+ private fun getAllPaymentMethods() {
+
+ paymentMethodViewModel.getAllPaymentMethods()
+ .observe(viewLifecycleOwner) { paymentMethods ->
+
+ if (isForEdit && selectPaymentMethodAdapter.currentList.isEmpty()) {
+
+ receivedIncome.linkedPaymentMethods?.let { alreadySelectedPM ->
+ paymentMethods.forEach { pm ->
+ if (alreadySelectedPM.contains(pm.key)) {
+ pm.isSelected = true
+ }
+ }
+ }
+ }
+
+ val addPaymentMethod = PaymentMethod(
+ key = Constants.ADD_PAYMENT_METHOD_KEY,
+ paymentMethod = getString(R.string.add),
+ uid = "",
+ isSynced = false,
+ isSelected = false
+ )
+
+ selectPaymentMethodAdapter.submitList(paymentMethods + listOf(addPaymentMethod))
+ }
+
+ }
+
+ private fun getIncomeByKey() {
+
+ incomeViewModel.getIncomeByKey(receivedIncomeKey).observe(viewLifecycleOwner) { income ->
+ receivedIncome = income
+ receivedMonth = receivedIncome.month
+ receivedYear = receivedIncome.year
+ updateUI()
+ }
+ }
+
+ private fun updateUI() {
+
+ if (this::receivedIncome.isInitialized) {
+
+ binding.apply {
+ insideEditTextIncome.setText(receivedIncome.income.toString())
+ insideEditTextSource.setText(receivedIncome.source)
+ }
+ }
+ }
+
+
+ private fun initListener() {
+
+ binding.toolbar.setNavigationOnClickListener {
+
+ if (mListener != null) {
+ mListener!!.onIncomeBottomSheetDismissed(false)
+ }
+ dismiss()
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_save_btn).setOnMenuItemClickListener {
+
+ if (isFormValid()) {
+
+ initIncome()
+ }
+ true
+ }
+ }
+
+ private fun initIncome() {
+
+ var income = Income()
+
+ if (isForEdit) {
+
+ income = receivedIncome.copy()
+ } else {
+
+ income.apply {
+ this.created = System.currentTimeMillis()
+ this.uid = Functions.getUid()!!
+ this.key = Functions.generateKey("_${Functions.getUid()}")
+ this.month = receivedMonth
+ this.year = receivedYear
+ this.monthYearString = this.generateMonthYearString()
+ this.isSynced = isInternetAvailable(requireContext())
+ }
+ }
+
+ income.source = binding.insideEditTextSource.text.toString().trim()
+ income.income = binding.insideEditTextIncome.text.toString().trim().toDouble()
+
+ val selectedPaymentMethods =
+ selectPaymentMethodAdapter.currentList.filter { pm -> pm.isSelected }
+ .map { pm -> pm.key }
+
+ income.linkedPaymentMethods =
+ selectedPaymentMethods.ifEmpty { listOf(Constants.PAYMENT_METHOD_OTHER_KEY) }
+
+ income.modified = System.currentTimeMillis()
+
+ if (!isForEdit) {
+ incomeViewModel.insertIncome(income)
+ } else {
+ incomeViewModel.updateIncome(
+ receivedIncome, income
+ )
+ }
+
+ if (mListener != null) {
+
+ mListener!!.onIncomeBottomSheetDismissed(true)
+ }
+
+ dismiss()
+ }
+
+ private fun isFormValid(): Boolean {
+
+ if (!binding.insideEditTextIncome.isTextValid()) {
+
+ binding.incomeET.error = EDIT_TEXT_EMPTY_MESSAGE
+ return false
+ }
+
+ if (!binding.insideEditTextSource.isTextValid()) {
+ binding.sourceET.error = EDIT_TEXT_EMPTY_MESSAGE
+ return false
+ }
+
+ return binding.incomeET.error == null && binding.sourceET.error == null
+ }
+
+ private fun textWatchers() {
+
+ binding.insideEditTextIncome.onTextChangedListener { s ->
+
+ if (!s?.toString().isValid()) {
+
+ binding.incomeET.error = EDIT_TEXT_EMPTY_MESSAGE
+ } else {
+
+ if (s?.toString()?.trim()?.toDouble()!! <= 0.0) {
+ binding.incomeET.error = getString(R.string.income_should_be_grater_than_0)
+ } else {
+ binding.incomeET.error = null
+ }
+ }
+ }
+
+ binding.insideEditTextSource.onTextChangedListener { s ->
+
+ if (!s?.toString().isValid()) {
+
+ binding.sourceET.error = EDIT_TEXT_EMPTY_MESSAGE
+ } else {
+
+ binding.sourceET.error = null
+ }
+ }
+ }
+
+ interface OnIncomeBottomSheetDismissListener {
+
+ fun onIncomeBottomSheetDismissed(
+ isIncomeAdded: Boolean
+ )
+ }
+
+ fun setOnBottomSheetDismissListener(listener: OnIncomeBottomSheetDismissListener) {
+
+ mListener = listener
+ }
+
+
+ companion object {
+ @JvmStatic
+ fun newInstance(bundle: Bundle): AddIncomeBottomSheetFragment {
+ val fragment = AddIncomeBottomSheetFragment()
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetAndIncomeGraphFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetAndIncomeGraphFragment.kt
new file mode 100644
index 00000000..e362cdb6
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetAndIncomeGraphFragment.kt
@@ -0,0 +1,243 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
+import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.data.filter.ExpenseFilterDto
+import com.rohitthebest.manageyourrenters.databinding.FragmentBudgetAndIncomeGraphBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
+import com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.ShowPaymentMethodSelectorDialogFragment
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetAndIncomeGraphViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.convertToJsonString
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.setListToSpinner
+import com.rohitthebest.manageyourrenters.utils.show
+import dagger.hilt.android.AndroidEntryPoint
+
+private const val TAG = "BudgetAndIncomeGraphFragment"
+
+@AndroidEntryPoint
+class BudgetAndIncomeGraphFragment : Fragment(R.layout.fragment_budget_and_income_graph),
+ ShowPaymentMethodSelectorDialogFragment.OnClickListener {
+
+ private var _binding: FragmentBudgetAndIncomeGraphBinding? = null
+ private val binding get() = _binding!!
+
+ private val budgetAndIncomeGraphViewModel by viewModels()
+ private val budgetViewModel by viewModels()
+
+ private var oldestYearWhenBudgetWasSaved = 2000
+ private var selectedYear = 2020
+
+ private var expenseFilterDto: ExpenseFilterDto? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentBudgetAndIncomeGraphBinding.bind(view)
+
+ initUI()
+ initListeners()
+
+ observeBarDataForBudgetIncomeAndExpense()
+ }
+
+ private fun observeBarDataForBudgetIncomeAndExpense() {
+
+ budgetAndIncomeGraphViewModel.incomeBudgetAndExpenseBarEntryData.observe(viewLifecycleOwner) { data ->
+
+ binding.progressBar.show()
+
+ data[FirestoreCollectionsConstants.BUDGETS]?.let { budgets ->
+ data[FirestoreCollectionsConstants.INCOMES]?.let { incomes ->
+ data[FirestoreCollectionsConstants.EXPENSES]?.let { expenses ->
+ initializeBarDataAndGraph(
+ budgets,
+ incomes,
+ expenses
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun initUI() {
+
+ budgetViewModel.getTheOldestSavedBudgetYear().observe(viewLifecycleOwner) { year ->
+ try {
+ oldestYearWhenBudgetWasSaved = year ?: WorkingWithDateAndTime.getCurrentYear()
+
+ // set up year spinner
+ initYearSpinner(
+ oldestYearWhenBudgetWasSaved,
+ WorkingWithDateAndTime.getCurrentYear()
+ )
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun initYearSpinner(startYear: Int, endYear: Int) {
+
+ val yearList = ArrayList()
+
+ for (year in endYear downTo startYear) {
+ yearList.add(year)
+ }
+
+ binding.yearSpinner.setListToSpinner(
+ context = requireContext(),
+ list = yearList,
+ position = { position ->
+
+ selectedYear = yearList[position]
+ handleGraphData()
+ }, {}
+ )
+ }
+
+ private fun handleGraphData() {
+
+ budgetAndIncomeGraphViewModel.getBarEntryDataForIncomeBudgetAndExpenseByYear(
+ selectedYear, expenseFilterDto?.paymentMethods ?: emptyList()
+ )
+ }
+
+
+ private fun initializeBarDataAndGraph(
+ budgetBarEntry: MutableList,
+ incomeBarEntry: MutableList,
+ expenseBarEntry: MutableList
+ ) {
+
+ val barDataSetBudget = BarDataSet(budgetBarEntry, getString(R.string.budget))
+ barDataSetBudget.color = ContextCompat.getColor(requireContext(), R.color.blue_text_color)
+
+ val barDataSetIncome = BarDataSet(incomeBarEntry, getString(R.string.income))
+ barDataSetIncome.color = ContextCompat.getColor(requireContext(), R.color.color_green)
+
+ val barDataSetExpense = BarDataSet(expenseBarEntry, getString(R.string.expense))
+ barDataSetExpense.color = ContextCompat.getColor(requireContext(), R.color.color_Red)
+
+ val barData = BarData(barDataSetIncome, barDataSetBudget, barDataSetExpense)
+
+ binding.chart.data = barData
+ val months = resources.getStringArray(R.array.months)
+
+ val xAxis = binding.chart.xAxis
+ xAxis.valueFormatter = IndexAxisValueFormatter(months)
+ xAxis.setCenterAxisLabels(true)
+ xAxis.position = XAxis.XAxisPosition.BOTTOM
+ xAxis.granularity = 1F
+ xAxis.isGranularityEnabled = true
+
+ binding.chart.isDragEnabled = true
+ binding.chart.setVisibleXRangeMaximum(3f)
+
+ val barSpace = 0.05f
+ val groupSpace = 0.31f
+ barData.barWidth = 0.18f
+
+ binding.chart.apply {
+ xAxis.axisMinimum = 0f
+ xAxis.axisMaximum =
+ 0 + binding.chart.barData.getGroupWidth(groupSpace, barSpace) * 12
+ axisLeft.axisMinimum = 0f
+
+ groupBars(0f, groupSpace, barSpace)
+ description.text = context.getString(R.string.income_vs_budget_vs_expense)
+ animateY(600)
+ }
+
+ binding.toolbar.subtitle = requireContext().getString(R.string.income_vs_budget_vs_expense)
+ binding.progressBar.hide()
+ binding.chart.invalidate()
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_saving_growth).setOnMenuItemClickListener {
+
+ showToast(requireContext(), getString(R.string.coming_soon))
+ true
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_filter_income_budget_graph_by_paymentMethods)
+ .setOnMenuItemClickListener {
+
+ showPaymentMethodSelectorDialog()
+
+ true
+ }
+
+ }
+
+ private fun showPaymentMethodSelectorDialog() {
+
+ requireActivity().supportFragmentManager.let { fragmentManager ->
+
+ val bundle = Bundle()
+ bundle.putString(
+ Constants.EXPENSE_FILTER_KEY,
+ if (expenseFilterDto == null) "" else expenseFilterDto.convertToJsonString()
+ )
+
+ ShowPaymentMethodSelectorDialogFragment.newInstance(
+ bundle
+ ).apply {
+ show(fragmentManager, TAG)
+ }.setOnClickListener(this)
+ }
+ }
+
+ override fun onFilterApply(selectedPaymentMethods: List?) {
+
+ binding.toolbar.menu.findItem(R.id.menu_filter_income_budget_graph_by_paymentMethods)
+ .apply {
+
+ if (selectedPaymentMethods.isNullOrEmpty()) {
+ this.icon =
+ ContextCompat.getDrawable(
+ requireContext(),
+ R.drawable.baseline_filter_list_24
+ )
+
+ expenseFilterDto = null
+
+ } else {
+ this.icon = ContextCompat.getDrawable(
+ requireContext(),
+ R.drawable.baseline_filter_list_colored_24
+ )
+
+ expenseFilterDto = ExpenseFilterDto()
+ expenseFilterDto!!.isPaymentMethodEnabled = true
+ expenseFilterDto!!.paymentMethods = selectedPaymentMethods
+ }
+ }
+
+ handleGraphData()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetAndIncomeOverviewFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetAndIncomeOverviewFragment.kt
new file mode 100644
index 00000000..bd619fe8
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetAndIncomeOverviewFragment.kt
@@ -0,0 +1,662 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.view.View
+import android.widget.PopupMenu
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome.BudgetRVAdapter
+import com.rohitthebest.manageyourrenters.data.CustomDateRange
+import com.rohitthebest.manageyourrenters.data.ShowExpenseBottomSheetTagsEnum
+import com.rohitthebest.manageyourrenters.data.filter.BudgetAndIncomeExpenseFilter
+import com.rohitthebest.manageyourrenters.data.filter.ExpenseFilterDto
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.databinding.FragmentBudgetBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.ui.fragments.MonthAndYearPickerDialog
+import com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.ShowPaymentMethodSelectorDialogFragment
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.ExpenseViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.IncomeViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.changeVisibilityOfViewOnScrolled
+import com.rohitthebest.manageyourrenters.utils.convertToJsonString
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.isNotValid
+import com.rohitthebest.manageyourrenters.utils.show
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "BudgetAndIncomeFragment"
+
+@AndroidEntryPoint
+class BudgetAndIncomeOverviewFragment : Fragment(R.layout.fragment_budget), View.OnClickListener,
+ BudgetRVAdapter.OnClickListener, MonthAndYearPickerDialog.OnMonthAndYearDialogDismissListener,
+ ShowPaymentMethodSelectorDialogFragment.OnClickListener,
+ AddBudgetLimitBottomSheetFragment.OnBottomSheetDismissListener {
+
+ private var _binding: FragmentBudgetBinding? = null
+ private val binding get() = _binding!!
+
+ private val budgetViewModel by viewModels()
+ private val expenseViewModel by viewModels()
+ private val incomeViewModel by viewModels()
+
+ private var selectedMonth: Int = 0
+ private var selectedYear: Int = 0
+ private var oldestYearWhenBudgetWasSaved = 2000
+ private var monthList: List = emptyList()
+
+ private lateinit var budgetAdapter: BudgetRVAdapter
+
+ private var expenseFilterDto: ExpenseFilterDto? = null
+
+ private var totalIncome = 0.0
+ private var totalExpense = 0.0
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentBudgetBinding.bind(view)
+
+ monthList = resources.getStringArray(R.array.months).toList()
+ budgetAdapter = BudgetRVAdapter()
+
+ initListeners()
+ setUpRecyclerView()
+
+ binding.progressBar.show()
+
+ observeBudgetList()
+
+ initUI()
+ }
+
+ private fun observeBudgetList() {
+
+ budgetViewModel.allBudgetListByMonthAndYear.observe(viewLifecycleOwner) { budgets ->
+
+ binding.noBudgetAddedTV.isVisible = budgets.isEmpty()
+
+ if (budgets.isNotEmpty()) {
+ binding.iabAddBudgetFAB.text = getString(R.string.modify_budgets)
+ } else {
+ binding.iabAddBudgetFAB.text = getString(R.string.add_budget)
+ }
+
+ budgetAdapter.submitList(budgets)
+ binding.progressBar.hide()
+ }
+ }
+
+ private fun setUpRecyclerView() {
+
+ binding.iabBudgetRV.apply {
+
+ setHasFixedSize(true)
+ layoutManager =
+ LinearLayoutManager(requireContext())
+ adapter = budgetAdapter
+ changeVisibilityOfViewOnScrolled(binding.iabAddBudgetFAB)
+ }
+
+ budgetAdapter.setOnClickListener(this)
+ }
+
+ override fun onItemClick(budget: Budget) {
+
+ showExpensesInBottomSheet(budget.expenseCategoryKey)
+ }
+
+ override fun onDetailsButtonClicked(budget: Budget) {
+
+ val action =
+ BudgetAndIncomeOverviewFragmentDirections.actionBudgetAndIncomeFragmentToBudgetOverviewFragment(
+ budget.key,
+ selectedMonth,
+ selectedYear
+ )
+ findNavController().navigate(action)
+ }
+
+
+ private var adapterPosition = 0
+ override fun onMenuBtnClick(budget: Budget, view: View, position: Int) {
+
+ adapterPosition = position
+ val popupMenu = PopupMenu(requireActivity(), view)
+
+ popupMenu.menuInflater.inflate(R.menu.budget_overview_item_menu, popupMenu.menu)
+
+ popupMenu.show()
+
+ popupMenu.setOnMenuItemClickListener {
+
+ return@setOnMenuItemClickListener when (it.itemId) {
+
+ R.id.menuEditBudgetLimit_bo -> {
+
+ handleEditBudgetMenu(budget)
+ true
+ }
+
+ R.id.menuAddExpense_bo -> {
+
+ handleAddExpenseMenu(budget)
+ true
+ }
+
+ else -> {
+ false
+ }
+ }
+ }
+
+ }
+
+
+ private fun handleAddExpenseMenu(budget: Budget) {
+ val action =
+ BudgetAndIncomeOverviewFragmentDirections.actionBudgetAndIncomeFragmentToAddEditExpense(
+ budget.expenseCategoryKey, ""
+ )
+ findNavController().navigate(action)
+ }
+
+ private fun handleEditBudgetMenu(budget: Budget) {
+
+ val bundle = Bundle()
+ bundle.putBoolean(Constants.IS_FOR_EDIT, true)
+ bundle.putString(Constants.DOCUMENT_KEY, budget.key)
+
+ requireActivity().supportFragmentManager.let { fm ->
+
+ AddBudgetLimitBottomSheetFragment.newInstance(bundle)
+ .apply {
+ show(fm, TAG)
+ }.setOnBottomSheetDismissListener(this)
+ }
+ }
+
+ override fun onBottomSheetDismissed(isBudgetLimitAdded: Boolean) {
+
+ if (isBudgetLimitAdded) {
+ lifecycleScope.launch {
+ delay(200)
+ handleUiAfterChange()
+ }
+ }
+ }
+
+ private fun initUI() {
+
+ selectedMonth = WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = WorkingWithDateAndTime.getCurrentYear()
+
+ binding.iabDateTV.text =
+ getString(R.string.month_and_year, monthList[selectedMonth], selectedYear.toString())
+
+ binding.progressBar.show()
+
+ lifecycleScope.launch {
+ delay(300)
+ handleUiAfterChange()
+ }
+
+ budgetViewModel.getTheOldestSavedBudgetYear().observe(viewLifecycleOwner) { year ->
+ try {
+ if (year != null) {
+ oldestYearWhenBudgetWasSaved = year
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.nextMonthBtn.setOnClickListener(this)
+ binding.previousMonthBtn.setOnClickListener(this)
+ binding.iabIncomeAddBtn.setOnClickListener(this)
+ binding.iabBudgetMCV.setOnClickListener(this)
+ binding.iabSavingMCV.setOnClickListener(this)
+ binding.iabExpenseMCV.setOnClickListener(this)
+ binding.iabIncomeMCV.setOnClickListener(this)
+ binding.iabAddBudgetFAB.setOnClickListener(this)
+ binding.monthMCV.setOnClickListener(this)
+
+ binding.toolbar.menu.findItem(R.id.menu_filter_income_and_budget_overview)
+ .setOnMenuItemClickListener {
+
+ showPaymentMethodSelectorDialog()
+
+ true
+ }
+
+ binding.toolbar.menu.findItem(R.id.menu_budget_and_income_graph)
+ .setOnMenuItemClickListener {
+
+ openGraphFragment()
+ true
+ }
+ }
+
+ private fun openGraphFragment() {
+
+ findNavController().navigate(R.id.action_budgetAndIncomeFragment_to_budgetAndIncomeGraphFragment)
+ }
+
+ private fun showPaymentMethodSelectorDialog() {
+
+ requireActivity().supportFragmentManager.let { fragmentManager ->
+
+ val bundle = Bundle()
+ bundle.putString(
+ Constants.EXPENSE_FILTER_KEY,
+ if (expenseFilterDto == null) "" else expenseFilterDto.convertToJsonString()
+ )
+
+ ShowPaymentMethodSelectorDialogFragment.newInstance(
+ bundle
+ ).apply {
+ show(fragmentManager, TAG)
+ }.setOnClickListener(this)
+ }
+ }
+
+ override fun onFilterApply(selectedPaymentMethods: List?) {
+
+ binding.toolbar.menu.findItem(R.id.menu_filter_income_and_budget_overview).apply {
+
+ if (selectedPaymentMethods.isNullOrEmpty()) {
+ this.icon =
+ ContextCompat.getDrawable(requireContext(), R.drawable.baseline_filter_list_24)
+
+ expenseFilterDto = null
+
+ } else {
+ this.icon = ContextCompat.getDrawable(
+ requireContext(),
+ R.drawable.baseline_filter_list_colored_24
+ )
+
+ expenseFilterDto = ExpenseFilterDto()
+ expenseFilterDto!!.isPaymentMethodEnabled = true
+ expenseFilterDto!!.paymentMethods = selectedPaymentMethods
+ }
+ }
+
+ // apply payment filter on income and expense
+ handleUiAfterChange()
+ }
+
+
+ override fun onClick(v: View?) {
+
+ when (v?.id) {
+
+ binding.nextMonthBtn.id -> {
+
+ handleNextDateButton()
+ }
+
+ binding.previousMonthBtn.id -> {
+ handlePreviousDateButton()
+ }
+
+ binding.iabIncomeAddBtn.id -> {
+
+ val bundle = Bundle()
+
+ bundle.putBoolean(Constants.IS_FOR_EDIT, false)
+ bundle.putInt(Constants.INCOME_MONTH_KEY, selectedMonth)
+ bundle.putInt(Constants.INCOME_YEAR_KEY, selectedYear)
+
+ requireActivity().supportFragmentManager.let { fragmentManager ->
+
+ AddIncomeBottomSheetFragment.newInstance(
+ bundle
+ ).apply {
+ show(fragmentManager, TAG)
+ }
+ }
+ }
+
+ binding.iabAddBudgetFAB.id -> {
+
+ val action =
+ BudgetAndIncomeOverviewFragmentDirections.actionBudgetAndIncomeFragmentToAddBudgetFragment(
+ selectedMonth, selectedYear
+ )
+
+ findNavController().navigate(action)
+ }
+
+ binding.iabIncomeMCV.id -> {
+
+ val action =
+ BudgetAndIncomeOverviewFragmentDirections.actionBudgetAndIncomeFragmentToIncomeFragment(
+ selectedMonth, selectedYear
+ )
+ findNavController().navigate(action)
+ }
+
+ binding.monthMCV.id -> {
+
+ handleMonthAndYearSelection()
+ }
+
+ binding.iabExpenseMCV.id -> {
+ showExpensesInBottomSheet()
+ }
+ }
+ }
+
+ private fun showExpensesInBottomSheet(categoryKey: String = "") {
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ selectedMonth, selectedYear
+ )
+
+ if (categoryKey.isNotValid()) {
+
+ // show all the expenses of the category for which budget is added
+
+ budgetViewModel.getAllExpenseCategoryOfBudgetsByMonthAndYear(
+ selectedMonth, selectedYear
+ )
+ .observe(viewLifecycleOwner) { categoryKeys ->
+
+ if (categoryKeys.isNotEmpty()) {
+
+ val action =
+ BudgetAndIncomeOverviewFragmentDirections.actionBudgetAndIncomeFragmentToShowExpenseBottomSheetFragment(
+ null,
+ CustomDateRange.CUSTOM_DATE_RANGE,
+ datePairForExpense.first,
+ datePairForExpense.second,
+ ShowExpenseBottomSheetTagsEnum.BUDGET_AND_INCOME_FRAGMENT,
+ BudgetAndIncomeExpenseFilter(
+ categoryKeys,
+ if (expenseFilterDto == null) emptyList() else expenseFilterDto!!.paymentMethods
+ )
+ )
+
+ findNavController().navigate(action)
+
+ } else {
+ showToast(
+ requireContext(),
+ getString(R.string.click_on_add_budget_button_to_add_budgets_for_this_month)
+ )
+ }
+ }
+
+ } else {
+
+ // show all the expenses for a clicked budget category
+
+ val action =
+ BudgetAndIncomeOverviewFragmentDirections.actionBudgetAndIncomeFragmentToShowExpenseBottomSheetFragment(
+ null,
+ CustomDateRange.CUSTOM_DATE_RANGE,
+ datePairForExpense.first,
+ datePairForExpense.second,
+ ShowExpenseBottomSheetTagsEnum.BUDGET_AND_INCOME_FRAGMENT,
+ BudgetAndIncomeExpenseFilter(
+ listOf(categoryKey),
+ if (expenseFilterDto == null) emptyList() else expenseFilterDto!!.paymentMethods
+ )
+ )
+
+ findNavController().navigate(action)
+ }
+ }
+
+ private fun handleMonthAndYearSelection() {
+
+ val bundle = Bundle()
+ bundle.putInt(Constants.MONTH_YEAR_PICKER_MONTH_KEY, selectedMonth)
+ bundle.putInt(Constants.MONTH_YEAR_PICKER_YEAR_KEY, selectedYear)
+ bundle.putInt(
+ Constants.MONTH_YEAR_PICKER_MIN_YEAR_KEY,
+ oldestYearWhenBudgetWasSaved - 4
+ )
+ bundle.putInt(
+ Constants.MONTH_YEAR_PICKER_MAX_YEAR_KEY,
+ WorkingWithDateAndTime.getCurrentYear()
+ )
+
+ requireActivity().supportFragmentManager.let { fm ->
+ MonthAndYearPickerDialog.newInstance(
+ bundle
+ ).apply {
+ show(fm, TAG)
+ }
+ }.setOnMonthAndYearDialogDismissListener(this)
+ }
+
+ override fun onMonthAndYearDialogDismissed(
+ isMonthAndYearSelected: Boolean,
+ selectedMonth: Int,
+ selectedYear: Int
+ ) {
+
+ if (isMonthAndYearSelected) {
+
+ this.selectedMonth = selectedMonth
+ this.selectedYear = selectedYear
+ handleUiAfterChange()
+ }
+ }
+
+
+ private fun handlePreviousDateButton() {
+
+ if (selectedMonth == 0) {
+ selectedYear -= 1
+ }
+
+ selectedMonth = WorkingWithDateAndTime.getPreviousMonth(selectedMonth)
+
+ handleUiAfterChange()
+ }
+
+ private fun handleNextDateButton() {
+
+ if (selectedMonth == 11) {
+ selectedYear += 1
+ }
+
+ selectedMonth = WorkingWithDateAndTime.getNextMonth(selectedMonth)
+ handleUiAfterChange()
+ }
+
+ private fun handleUiAfterChange() {
+
+ binding.progressBar.show()
+
+ setMonthAndYearInTextView()
+
+ // get all budgets (this list is being observed in the method observeBudgetList())
+ budgetViewModel.getAllBudgetsByMonthAndYear(
+ selectedMonth,
+ selectedYear,
+ if (expenseFilterDto == null) emptyList() else expenseFilterDto!!.paymentMethods
+ )
+
+ binding.progressBar.show()
+
+ lifecycleScope.launch {
+ delay(350)
+ budgetAdapter.notifyDataSetChanged()
+ }
+
+ showTotalExpense()
+ showTotalBudget()
+ showTotalIncomeAdded()
+
+ lifecycleScope.launch {
+ delay(250)
+ showTotalSavings()
+ }
+ }
+
+ private fun showTotalSavings() {
+
+ binding.iabSavingValueTV.text = (totalIncome - totalExpense).format(2)
+ }
+
+ private fun showTotalIncomeAdded() {
+
+ if (expenseFilterDto != null && expenseFilterDto!!.paymentMethods.isNotEmpty()) {
+
+ incomeViewModel.getAllIncomesByMonthAndYear(selectedMonth, selectedYear)
+ .observe(viewLifecycleOwner) { incomes ->
+
+ try {
+ if (incomes.isEmpty()) {
+ updateIncomeTotalUI(0.0)
+ } else {
+ val tempIncome = incomeViewModel.applyFilterByPaymentMethods(
+ expenseFilterDto!!.paymentMethods, incomes
+ )
+
+ updateIncomeTotalUI(tempIncome.sumOf { it.income })
+ }
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ totalIncome = 0.0
+ binding.iabIncomeValueTV.text = getString(R.string._0_0)
+ }
+ }
+ } else {
+ incomeViewModel.getTotalIncomeAddedByMonthAndYear(
+ selectedMonth, selectedYear
+ ).observe(viewLifecycleOwner) { totalIncomeAdded ->
+
+ try {
+ updateIncomeTotalUI(totalIncomeAdded)
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ totalIncome = 0.0
+ binding.iabIncomeValueTV.text = getString(R.string._0_0)
+ }
+ }
+ }
+ }
+
+ private fun updateIncomeTotalUI(totalIncomeAdded: Double) {
+
+ totalIncome = totalIncomeAdded
+ binding.iabIncomeValueTV.text = totalIncomeAdded.format(2)
+ showTotalSavings()
+ }
+
+
+ private fun showTotalExpense() {
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ selectedMonth, selectedYear
+ )
+
+ budgetViewModel.getAllExpenseCategoryOfBudgetsByMonthAndYear(
+ selectedMonth, selectedYear
+ )
+ .observe(viewLifecycleOwner) { categoryKeys ->
+
+ if (categoryKeys.isNotEmpty()) {
+
+ if (expenseFilterDto != null && expenseFilterDto!!.paymentMethods.isNotEmpty()) {
+
+ // get total expense by filtering the list by paymentMethods
+ expenseViewModel.getExpenseByCategoryKeysAndDateRange(
+ categoryKeys,
+ datePairForExpense.first,
+ datePairForExpense.second + Constants.ONE_DAY_MILLISECONDS
+ ).observe(viewLifecycleOwner) { expenses ->
+
+ if (expenses.isEmpty()) {
+ updateExpenseTotalUI(0.0)
+ } else {
+ val tempExpense = expenseViewModel.applyFilterByPaymentMethods(
+ expenseFilterDto!!.paymentMethods,
+ expenses
+ )
+
+ updateExpenseTotalUI(tempExpense.sumOf { it.amount })
+ }
+ }
+
+ } else {
+
+ expenseViewModel.getTotalExpenseByCategoryKeysAndDateRange(
+ categoryKeys,
+ datePairForExpense.first,
+ datePairForExpense.second + Constants.ONE_DAY_MILLISECONDS
+ ).observe(viewLifecycleOwner) { total ->
+
+ try {
+ updateExpenseTotalUI(total)
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ totalExpense = 0.0
+ binding.iabExpenseValueTV.text = getString(R.string._0_0)
+ }
+ }
+ }
+ } else {
+ binding.iabExpenseValueTV.text = getString(R.string._0_0)
+ totalExpense = 0.0
+ }
+ }
+ }
+
+ private fun updateExpenseTotalUI(total: Double) {
+
+ totalExpense = total
+ binding.iabExpenseValueTV.text = total.format(2)
+ showTotalSavings()
+ }
+
+ private fun showTotalBudget() {
+
+ budgetViewModel.getTotalBudgetByMonthAndYear(selectedMonth, selectedYear)
+ .observe(viewLifecycleOwner) { totalBudget ->
+ try {
+ binding.iabBudgetValueTV.text = totalBudget.format(2)
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ binding.iabBudgetValueTV.text = getString(R.string._0_0)
+ }
+ }
+ }
+
+
+ private fun setMonthAndYearInTextView() {
+
+ binding.iabDateTV.text =
+ getString(R.string.month_and_year, monthList[selectedMonth], selectedYear.toString())
+ }
+
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetOverviewFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetOverviewFragment.kt
new file mode 100644
index 00000000..9195851e
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/BudgetOverviewFragment.kt
@@ -0,0 +1,300 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
+import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.databinding.BudgetOverviewLayoutBinding
+import com.rohitthebest.manageyourrenters.databinding.FragmentBudgetOverviewBinding
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetAndIncomeGraphViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.getAppropriateBudgetSuggestionOrMessage
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.setImageToImageViewUsingGlide
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.changeTextColor
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.isNotValid
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class BudgetOverviewFragment : Fragment(R.layout.fragment_budget_overview) {
+
+ private var _binding: FragmentBudgetOverviewBinding? = null
+ private val binding get() = _binding!!
+
+ private val budgetViewModel by viewModels()
+ private val budgetAndIncomeGraphViewModel by viewModels()
+
+ private var receivedBudgetKey = ""
+ private var receivedMonth = 0
+ private var receivedYear = 0
+ private lateinit var receivedBudget: Budget
+ private lateinit var includeLayoutBinding: BudgetOverviewLayoutBinding
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentBudgetOverviewBinding.bind(view)
+
+ includeLayoutBinding = binding.includeLayout
+
+ initListeners()
+ getMessage()
+ observeBudget()
+ observeGraph()
+ }
+
+ private fun observeGraph() {
+
+ budgetAndIncomeGraphViewModel.budgetExpense.observe(viewLifecycleOwner) { data ->
+ setUpGraph(data)
+ }
+ }
+
+ private fun setUpGraph(dataEntry: List) {
+
+ val barDataSet = BarDataSet(dataEntry, getString(R.string.expenses))
+ barDataSet.color = ContextCompat.getColor(requireContext(), R.color.purple_500)
+
+ val barData = BarData(barDataSet)
+ includeLayoutBinding.budgetPerDayChart.data = barData
+
+ val days = ArrayList()
+
+ val numberOfDaysInMonth =
+ WorkingWithDateAndTime.getNumberOfDaysInMonth(receivedMonth, receivedYear)
+
+ val daysOfWeek =
+ WorkingWithDateAndTime.getDayOfWeeksForEntireMonth(receivedMonth, receivedYear)
+
+ for (i in 0..numberOfDaysInMonth) {
+
+ days.add(
+ when {
+
+ i.toString().startsWith("0") -> i.toString()
+ i.toString().endsWith("1") -> i.toString() + "st (${daysOfWeek[i - 1].second})"
+ i.toString().endsWith("2") -> i.toString() + "nd (${daysOfWeek[i - 1].second})"
+ i.toString().endsWith("3") -> i.toString() + "rd (${daysOfWeek[i - 1].second})"
+ else -> i.toString() + "th (${daysOfWeek[i - 1].second})"
+ }
+ )
+ }
+
+ val xAxis = includeLayoutBinding.budgetPerDayChart.xAxis
+ xAxis.valueFormatter = IndexAxisValueFormatter(days)
+ //xAxis.setCenterAxisLabels(true)
+ xAxis.position = XAxis.XAxisPosition.BOTTOM
+ xAxis.granularity = 1F
+ xAxis.isGranularityEnabled = true
+
+ barData.barWidth = 0.3f
+
+ includeLayoutBinding.budgetPerDayChart.apply {
+ xAxis.axisMinimum = 0f
+ animateY(600)
+ }
+
+ includeLayoutBinding.budgetPerDayChart.isDragEnabled = true
+ includeLayoutBinding.budgetPerDayChart.setVisibleXRangeMaximum(3f)
+
+
+ includeLayoutBinding.budgetPerDayChart.description.text = "Per day expenses"
+ includeLayoutBinding.budgetPerDayChart.invalidate()
+ }
+
+ private fun getMessage() {
+
+ if (!arguments?.isEmpty!!) {
+
+ val args = arguments?.let {
+ BudgetOverviewFragmentArgs.fromBundle(it)
+ }
+
+ receivedBudgetKey = args?.budgetKeyMessage ?: ""
+ receivedMonth = args?.month ?: 0
+ receivedYear = args?.year ?: 0
+
+ if (receivedBudgetKey.isNotValid() || receivedYear == 0) {
+ showToast(requireContext(), getString(R.string.something_went_wrong))
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ } else {
+
+ budgetViewModel.getBudgetByKeyWithACategoryAndExpenseDetails(
+ budgetKey = receivedBudgetKey,
+ month = receivedMonth,
+ year = receivedYear,
+ selectedPaymentMethods = emptyList()
+ )
+ }
+
+ }
+ }
+
+ private fun observeBudget() {
+
+ budgetViewModel.budgetByKey.observe(viewLifecycleOwner) { budget ->
+
+ receivedBudget = budget
+ updateUI()
+ budgetAndIncomeGraphViewModel.getEveryDayExpenseData(
+ budget.expenseCategoryKey, receivedMonth, receivedYear
+ )
+ }
+ }
+
+ private fun updateUI() {
+
+ if (!this::receivedBudget.isInitialized) {
+ showToast(requireContext(), getString(R.string.something_went_wrong))
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ } else {
+
+ binding.toolbar.title = receivedBudget.categoryName
+
+ includeLayoutBinding.apply {
+
+
+ budgetCatNameTV.text = receivedBudget.categoryName
+ setImageToImageViewUsingGlide(
+ requireContext(),
+ budgetCategoryIV,
+ receivedBudget.categoryImageUrl,
+ {},
+ {}
+ )
+
+ val numberOfDaysLeftInMonth =
+ WorkingWithDateAndTime.getNumberOfDaysLeftInAnyMonth(
+ receivedBudget.month, receivedBudget.year
+ )
+
+ var perDayExpense = if (numberOfDaysLeftInMonth != 0) {
+ (receivedBudget.budgetLimit - receivedBudget.currentExpenseAmount) / numberOfDaysLeftInMonth
+ } else {
+ 0.0
+ }
+
+ if (perDayExpense < 0) perDayExpense = 0.0
+
+ budgetPerDayTV.text = binding.root.context.getString(
+ R.string.budgetPerDay, perDayExpense.format(2),
+ numberOfDaysLeftInMonth.toString()
+ )
+
+ amountSpentBudgetTV.text = receivedBudget.currentExpenseAmount.format(2)
+ budgetLimitAmountTV.text = receivedBudget.budgetLimit.format(2)
+
+ val amountLeft = receivedBudget.budgetLimit - receivedBudget.currentExpenseAmount
+
+ when {
+
+ amountLeft > 0 -> amountLeftBudgetTV.text =
+ getString(R.string.you_still_have_left_in_your_budget, amountLeft.format(2))
+
+ amountLeft == 0.0 -> amountLeftBudgetTV.text = getString(R.string.limit_reached)
+
+ amountLeft < 0 -> getString(R.string.overspent)
+
+ }
+
+ val progressInPercent =
+ ((receivedBudget.currentExpenseAmount / receivedBudget.budgetLimit) * 100).toInt()
+
+ budgetProgressBar.max = receivedBudget.budgetLimit.toInt()
+
+ if (receivedBudget.currentExpenseAmount > receivedBudget.budgetLimit) {
+ budgetProgressBar.progress = receivedBudget.budgetLimit.toInt()
+ } else {
+ budgetProgressBar.progress = receivedBudget.currentExpenseAmount.toInt()
+ }
+
+ changeUIRelatedToProgress(progressInPercent)
+
+ }
+ }
+ }
+
+ private fun changeUIRelatedToProgress(progressInPercent: Int) {
+
+ // budget message
+ includeLayoutBinding.budgetMessageTV.text = getAppropriateBudgetSuggestionOrMessage(
+ requireContext(),
+ progressInPercent,
+ receivedBudget.currentExpenseAmount,
+ receivedBudget.budgetLimit
+ )
+
+ when {
+
+ (progressInPercent in 0..35) -> {
+ val colorGreen = ContextCompat.getColor(
+ requireContext(),
+ R.color.color_green
+ )
+ includeLayoutBinding.budgetProgressBar.progressTintList = ColorStateList.valueOf(
+ colorGreen
+ )
+
+ includeLayoutBinding.budgetMessageIV.setImageResource(
+ R.drawable.baseline_check_circle_green_24
+ )
+
+ }
+
+ (progressInPercent in 36..68) -> {
+ val colorYellow = ContextCompat.getColor(
+ requireContext(),
+ R.color.color_yellow
+ )
+ includeLayoutBinding.budgetProgressBar.progressTintList = ColorStateList.valueOf(
+ colorYellow
+ )
+
+ includeLayoutBinding.budgetMessageIV.setImageResource(
+ R.drawable.baseline_check_circle_yellow_24
+ )
+ }
+
+ else -> {
+ val colorRed = ContextCompat.getColor(
+ requireContext(),
+ R.color.color_Red
+ )
+ includeLayoutBinding.budgetProgressBar.progressTintList = ColorStateList.valueOf(
+ colorRed
+ )
+ includeLayoutBinding.budgetMessageIV.setImageResource(
+ R.drawable.baseline_check_circle_red_24
+ )
+ }
+ }
+
+ if (progressInPercent >= 80) {
+ includeLayoutBinding.budgetLimitAmountTV.changeTextColor(
+ requireContext(), R.color.color_white
+ )
+ }
+ }
+
+ private fun initListeners() {
+
+ binding.toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/ChooseMonthAndYearBottomSheetFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/ChooseMonthAndYearBottomSheetFragment.kt
new file mode 100644
index 00000000..37a4444c
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/ChooseMonthAndYearBottomSheetFragment.kt
@@ -0,0 +1,124 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome.SelectMonthAndYearAdapter
+import com.rohitthebest.manageyourrenters.databinding.FragmentChooseMonthAndYearBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.utils.Functions.Companion.showToast
+import com.rohitthebest.manageyourrenters.utils.convertJSONToStringList
+
+private const val TAG = "ChooseMonthAndYearBotto"
+
+class ChooseMonthAndYearBottomSheetFragment :
+ BottomSheetDialogFragment(R.layout.fragment_choose_month_and_year),
+ SelectMonthAndYearAdapter.OnClickListener {
+
+ private var _binding: FragmentChooseMonthAndYearBinding? = null
+ private val binding get() = _binding!!
+
+ private var mListener: OnBottomSheetDismissListener? = null
+ private lateinit var selectMonthAndYearAdapter: SelectMonthAndYearAdapter
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentChooseMonthAndYearBinding.bind(view)
+
+ selectMonthAndYearAdapter = SelectMonthAndYearAdapter()
+ setUpRecyclerView()
+
+ getMessage()
+
+ binding.toolbar.setNavigationOnClickListener {
+
+ if (mListener != null) {
+
+ mListener!!.onMonthAndYearSelectedForCopyingBudget(false, "")
+ }
+ dismiss()
+ }
+ }
+
+ private fun setUpRecyclerView() {
+
+ binding.monthAndYearRV.apply {
+
+ setHasFixedSize(true)
+ adapter = selectMonthAndYearAdapter
+ layoutManager = LinearLayoutManager(requireContext())
+ }
+
+ selectMonthAndYearAdapter.setOnClickListener(this)
+ }
+
+ override fun onMonthAndYearClicked(monthAndYear: String) {
+
+ if (mListener != null) {
+ mListener!!.onMonthAndYearSelectedForCopyingBudget(
+ true,
+ monthAndYear
+ )
+
+ dismiss()
+ }
+ }
+
+
+ private fun getMessage() {
+
+ if (!arguments?.isEmpty!!) {
+
+ arguments?.let { bundle ->
+
+ try {
+
+ val monthYearList =
+ convertJSONToStringList(bundle.getString(Constants.COPY_BUDGET_MONTH_AND_YEAR_KEY))
+
+ Log.d(TAG, "getMessage: monthYearString: $monthYearList")
+
+ selectMonthAndYearAdapter.submitList(monthYearList)
+
+ } catch (e: java.lang.Exception) {
+ e.printStackTrace()
+ requireContext().showToast(getString(R.string.something_went_wrong))
+ dismiss()
+ }
+ }
+ }
+
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance(bundle: Bundle): ChooseMonthAndYearBottomSheetFragment {
+ val fragment = ChooseMonthAndYearBottomSheetFragment()
+ fragment.arguments = bundle
+ return fragment
+ }
+ }
+
+ interface OnBottomSheetDismissListener {
+
+ fun onMonthAndYearSelectedForCopyingBudget(
+ isMonthAndYearSelected: Boolean,
+ selectedMonthYearString: String
+ )
+ }
+
+ fun setOnBottomSheetDismissListener(listener: OnBottomSheetDismissListener) {
+
+ mListener = listener
+ }
+
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/IncomeFragment.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/IncomeFragment.kt
new file mode 100644
index 00000000..593849bd
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/fragments/trackMoney/expense/budgetAndIncome/IncomeFragment.kt
@@ -0,0 +1,357 @@
+package com.rohitthebest.manageyourrenters.ui.fragments.trackMoney.expense.budgetAndIncome
+
+import android.os.Bundle
+import android.view.View
+import android.widget.PopupMenu
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.adapters.trackMoneyAdapters.expenseAdapters.budgetAndIncome.IncomeRVAdapter
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.databinding.FragmentIncomeBinding
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.ui.fragments.MonthAndYearPickerDialog
+import com.rohitthebest.manageyourrenters.ui.viewModels.BudgetViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.IncomeViewModel
+import com.rohitthebest.manageyourrenters.ui.viewModels.PaymentMethodViewModel
+import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.changeVisibilityOfFABOnScrolled
+import com.rohitthebest.manageyourrenters.utils.format
+import com.rohitthebest.manageyourrenters.utils.hide
+import com.rohitthebest.manageyourrenters.utils.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.show
+import com.rohitthebest.manageyourrenters.utils.showAlertDialogForDeletion
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "IncomeFragment"
+
+@AndroidEntryPoint
+class IncomeFragment : Fragment(R.layout.fragment_income), IncomeRVAdapter.OnClickListener,
+ MonthAndYearPickerDialog.OnMonthAndYearDialogDismissListener {
+
+ private var _binding: FragmentIncomeBinding? = null
+ private val binding get() = _binding!!
+
+ private val incomeViewModel by viewModels()
+ private val budgetViewModel by viewModels()
+ private val paymentMethodViewModel by viewModels()
+
+ private var selectedMonth: Int = 0
+ private var selectedYear: Int = 0
+ private var monthList: List = emptyList()
+
+ private lateinit var incomeRVAdapter: IncomeRVAdapter
+
+ private var oldestYearWhenBudgetWasSaved = 2000
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ _binding = FragmentIncomeBinding.bind(view)
+
+ monthList = resources.getStringArray(R.array.months).toList()
+ getMessage()
+ initListener()
+ }
+
+ private fun setUpRecyclerView() {
+
+ binding.incomeRV.apply {
+
+ setHasFixedSize(true)
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = incomeRVAdapter
+ changeVisibilityOfFABOnScrolled(binding.addIncomeFAB)
+ }
+
+ incomeRVAdapter.setOnClickListener(this)
+ }
+
+
+ override fun onItemClick(income: Income) {
+ // todo
+ }
+
+ override fun onIncomeItemMenuBtnClicked(income: Income, view: View) {
+
+ val popupMenu = PopupMenu(requireContext(), view)
+ popupMenu.menuInflater.inflate(R.menu.income_item_menu, popupMenu.menu)
+
+ popupMenu.show()
+
+ popupMenu.setOnMenuItemClickListener {
+
+ return@setOnMenuItemClickListener when (it.itemId) {
+
+ R.id.menu_income_edit -> {
+
+ handleEditIncomeMenu(income)
+ true
+ }
+
+ R.id.menu_delete_income -> {
+
+ deleteIncome(income)
+ true
+ }
+
+ else -> false
+ }
+
+ }
+ }
+
+ override fun onSyncBtnCLicked(income: Income, position: Int) {
+
+ if (requireContext().isInternetAvailable()) {
+
+ if (!income.isSynced) {
+
+ incomeViewModel.insertIncome(income)
+ incomeRVAdapter.notifyItemChanged(position)
+ }
+
+ } else {
+ Functions.showNoInternetMessage(requireContext())
+ }
+
+ }
+
+ private fun deleteIncome(income: Income) {
+
+ showAlertDialogForDeletion(
+ requireContext(),
+ {
+ incomeViewModel.deleteIncome(income)
+ it.dismiss()
+ },
+ {
+ it.dismiss()
+ }
+ )
+ }
+
+ private fun handleEditIncomeMenu(income: Income) {
+
+ showAddEditIncomeBottomSheetFragment(true, income.key)
+ }
+
+ private fun initListener() {
+
+ binding.toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.previousMonthBtn.setOnClickListener {
+ handlePreviousDateButton()
+ }
+
+ binding.nextMonthBtn.setOnClickListener {
+ handleNextDateButton()
+ }
+
+ binding.addIncomeFAB.setOnClickListener {
+
+ showAddEditIncomeBottomSheetFragment(false)
+
+ }
+
+ binding.monthMCV.setOnClickListener {
+ handleMonthAndYearSelection()
+ }
+ }
+
+ private fun handleMonthAndYearSelection() {
+
+ val bundle = Bundle()
+ bundle.putInt(Constants.MONTH_YEAR_PICKER_MONTH_KEY, selectedMonth)
+ bundle.putInt(Constants.MONTH_YEAR_PICKER_YEAR_KEY, selectedYear)
+ bundle.putInt(
+ Constants.MONTH_YEAR_PICKER_MIN_YEAR_KEY,
+ oldestYearWhenBudgetWasSaved - 4
+ )
+ bundle.putInt(
+ Constants.MONTH_YEAR_PICKER_MAX_YEAR_KEY,
+ WorkingWithDateAndTime.getCurrentYear()
+ )
+
+ requireActivity().supportFragmentManager.let { fm ->
+ MonthAndYearPickerDialog.newInstance(
+ bundle
+ ).apply {
+ show(fm, TAG)
+ }
+ }.setOnMonthAndYearDialogDismissListener(this)
+ }
+
+ override fun onMonthAndYearDialogDismissed(
+ isMonthAndYearSelected: Boolean,
+ selectedMonth: Int,
+ selectedYear: Int
+ ) {
+
+ if (isMonthAndYearSelected) {
+ this.selectedMonth = selectedMonth
+ this.selectedYear = selectedYear
+ handleUiAfterDateChange()
+ }
+ }
+
+
+ private fun showAddEditIncomeBottomSheetFragment(isForEdit: Boolean, incomeKey: String = "") {
+
+ val bundle = Bundle()
+
+ bundle.putBoolean(Constants.IS_FOR_EDIT, isForEdit)
+ bundle.putInt(Constants.INCOME_MONTH_KEY, selectedMonth)
+ bundle.putInt(Constants.INCOME_YEAR_KEY, selectedYear)
+
+ if (isForEdit) {
+
+ bundle.putString(Constants.DOCUMENT_KEY, incomeKey)
+ }
+
+ requireActivity().supportFragmentManager.let { fragmentManager ->
+
+ AddIncomeBottomSheetFragment.newInstance(
+ bundle
+ ).apply {
+ show(fragmentManager, TAG)
+ }
+ }
+ }
+
+ private fun getMessage() {
+
+ try {
+
+ if (!arguments?.isEmpty!!) {
+
+ val args = arguments?.let {
+ IncomeFragmentArgs.fromBundle(it)
+ }
+
+ selectedMonth = args?.monthMessage ?: WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = args?.yearMessage ?: WorkingWithDateAndTime.getCurrentYear()
+ } else {
+ selectedMonth = WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = WorkingWithDateAndTime.getCurrentYear()
+ }
+
+ lifecycleScope.launch {
+ delay(300)
+ initUI()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+
+ selectedMonth = WorkingWithDateAndTime.getCurrentMonth()
+ selectedYear = WorkingWithDateAndTime.getCurrentYear()
+ lifecycleScope.launch {
+ delay(300)
+ initUI()
+ }
+ }
+
+ }
+
+ private fun initUI() {
+
+ handleUiAfterDateChange()
+
+ budgetViewModel.getTheOldestSavedBudgetYear().observe(viewLifecycleOwner) { year ->
+ try {
+ if (year != null) {
+ oldestYearWhenBudgetWasSaved = year
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun handlePreviousDateButton() {
+
+ if (selectedMonth == 0) {
+ selectedYear -= 1
+ }
+
+ selectedMonth = WorkingWithDateAndTime.getPreviousMonth(selectedMonth)
+
+ handleUiAfterDateChange()
+ }
+
+ private fun handleNextDateButton() {
+
+ if (selectedMonth == 11) {
+ selectedYear += 1
+ }
+
+ selectedMonth = WorkingWithDateAndTime.getNextMonth(selectedMonth)
+ handleUiAfterDateChange()
+ }
+
+ private fun handleUiAfterDateChange() {
+
+ binding.monthAndYearTV.text =
+ getString(R.string.month_and_year, monthList[selectedMonth], selectedYear.toString())
+
+ paymentMethodViewModel.getAllPaymentMethods()
+ .observe(viewLifecycleOwner) { paymentMethods ->
+
+ incomeRVAdapter = IncomeRVAdapter(
+ linkedPaymentMethodsMap = paymentMethods.associate { it.key to it.paymentMethod })
+
+ setUpRecyclerView()
+ getAllIncomes()
+ }
+
+ getTotalIncome()
+ }
+
+ private fun getTotalIncome() {
+
+ incomeViewModel.getTotalIncomeAddedByMonthAndYear(
+ selectedMonth, selectedYear
+ ).observe(viewLifecycleOwner) { total ->
+
+ try {
+
+ binding.toolbar.subtitle = getString(R.string.total_with_arg, total.format(2))
+ } catch (e: NullPointerException) {
+
+ binding.toolbar.subtitle =
+ getString(R.string.total_with_arg, getString(R.string._0_0))
+ }
+ }
+ }
+
+ private fun getAllIncomes() {
+
+ incomeViewModel.getAllIncomesByMonthAndYear(selectedMonth, selectedYear)
+ .observe(viewLifecycleOwner) { incomes ->
+
+ if (incomes.isNotEmpty()) {
+
+ binding.incomeRV.show()
+ binding.noIncomeAddedTV.hide()
+ } else {
+ binding.incomeRV.hide()
+ binding.noIncomeAddedTV.show()
+ }
+
+ incomeRVAdapter.submitList(incomes)
+ getTotalIncome()
+ }
+ }
+
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+}
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/BudgetAndIncomeGraphViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/BudgetAndIncomeGraphViewModel.kt
new file mode 100644
index 00000000..d539253f
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/BudgetAndIncomeGraphViewModel.kt
@@ -0,0 +1,270 @@
+package com.rohitthebest.manageyourrenters.ui.viewModels
+
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import com.github.mikephil.charting.data.BarEntry
+import com.rohitthebest.manageyourrenters.data.MonthAndTotalSum
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
+import com.rohitthebest.manageyourrenters.repositories.BudgetRepository
+import com.rohitthebest.manageyourrenters.repositories.ExpenseRepository
+import com.rohitthebest.manageyourrenters.repositories.IncomeRepository
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+private const val TAG = "BudgetAndIncomeGraphViewModel"
+
+@HiltViewModel
+class BudgetAndIncomeGraphViewModel @Inject constructor(
+ app: Application,
+ private val expenseRepository: ExpenseRepository,
+ private val budgetRepository: BudgetRepository,
+ private val incomeRepository: IncomeRepository
+) : AndroidViewModel(app) {
+
+ private var _incomeBudgetAndExpenseBarEntryData =
+ MutableLiveData>>(
+ emptyMap()
+ )
+
+ val incomeBudgetAndExpenseBarEntryData: LiveData>>
+ get() = _incomeBudgetAndExpenseBarEntryData
+
+ fun getBarEntryDataForIncomeBudgetAndExpenseByYear(
+ year: Int,
+ paymentMethodKeys: List = emptyList()
+ ) {
+
+ viewModelScope.launch {
+
+ // budget
+ val budgetTotals = budgetRepository.getAllTotalBudgetByYear(year).first()
+ val budgetBarEntry = getBudgetBarEntry(budgetTotals)
+
+ // income
+ val incomeTotals = if (paymentMethodKeys.isNotEmpty()) {
+ incomeTotalAfterApplyingPaymentMethodFilter(
+ year, paymentMethodKeys
+ )
+ } else {
+ incomeRepository.getAllTotalIncomeByYear(year).first()
+ }
+
+ Log.d(
+ TAG,
+ "getBarEntryDataForIncomeBudgetAndExpenseByYear: incomeTotals: $incomeTotals"
+ )
+
+ val incomeBarEntry = getIncomeBarEntry(incomeTotals)
+
+ // expense
+ val monthAndTotalExpenseList: ArrayList = ArrayList()
+ for (i in 0..11) {
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ i, year
+ )
+
+ val categoryKeys =
+ budgetRepository.getExpenseCategoryKeysOfAllBudgetsByMonthAndYear(
+ i, year
+ ).first()
+
+
+ val expenseTotal: Double = try {
+
+ if (paymentMethodKeys.isNotEmpty()) {
+ getExpenseTotalAfterApplyingPaymentMethodFilter(
+ datePairForExpense, categoryKeys, paymentMethodKeys
+ )
+ } else {
+ expenseRepository.getTotalExpenseByCategoryKeysAndDateRange(
+ categoryKeys,
+ datePairForExpense.first,
+ datePairForExpense.second + Constants.ONE_DAY_MILLISECONDS
+ ).first()
+ }
+ } catch (e: NullPointerException) {
+ 0.0
+ }
+
+ monthAndTotalExpenseList.add(MonthAndTotalSum(i, expenseTotal))
+ }
+
+ val expenseBarEntry = getExpenseBarEntry(monthAndTotalExpenseList)
+
+ val consolidatedBarEntries: HashMap> = HashMap()
+
+ consolidatedBarEntries[FirestoreCollectionsConstants.BUDGETS] = budgetBarEntry
+ consolidatedBarEntries[FirestoreCollectionsConstants.INCOMES] = incomeBarEntry
+ consolidatedBarEntries[FirestoreCollectionsConstants.EXPENSES] = expenseBarEntry
+
+ Log.d(
+ TAG,
+ "getBarEntryDataForIncomeBudgetAndExpenseByYear: ${
+ consolidatedBarEntries[FirestoreCollectionsConstants.EXPENSES]
+ }"
+ )
+
+ _incomeBudgetAndExpenseBarEntryData.value = consolidatedBarEntries
+ }
+ }
+
+ private fun getBudgetBarEntry(budgetTotals: List): ArrayList {
+
+ val budgetLimitList = ArrayList()
+ val monthAndBudgetSumMap = budgetTotals.associate { it.month to it.total }
+
+ for (i in 1..12) {
+
+ if (monthAndBudgetSumMap.contains(i - 1)) {
+ budgetLimitList.add(BarEntry(i.toFloat(), monthAndBudgetSumMap[i - 1]!!.toFloat()))
+ } else {
+ budgetLimitList.add(BarEntry(i.toFloat(), 0f))
+ }
+ }
+
+ return budgetLimitList
+ }
+
+ private fun getIncomeBarEntry(incomeTotals: List): ArrayList {
+
+ val incomeList = ArrayList()
+ val monthAndIncomeSumMap = incomeTotals.associate { it.month to it.total }
+
+ for (i in 1..12) {
+
+ if (monthAndIncomeSumMap.contains(i - 1)) {
+ incomeList.add(BarEntry(i.toFloat(), monthAndIncomeSumMap[i - 1]!!.toFloat()))
+ } else {
+ incomeList.add(BarEntry(i.toFloat(), 0f))
+ }
+ }
+
+ return incomeList
+ }
+
+ private fun getExpenseBarEntry(expenseTotals: List): ArrayList {
+
+ val expenseList = ArrayList()
+ val monthAndIncomeSumMap = expenseTotals.associate { it.month to it.total }
+
+ for (i in 1..12) {
+
+ if (monthAndIncomeSumMap.contains(i - 1)) {
+ expenseList.add(BarEntry(i.toFloat(), monthAndIncomeSumMap[i - 1]!!.toFloat()))
+ } else {
+ expenseList.add(BarEntry(i.toFloat(), 0f))
+ }
+ }
+
+ return expenseList
+ }
+
+ private suspend fun incomeTotalAfterApplyingPaymentMethodFilter(
+ year: Int,
+ paymentMethods: List
+ ): List {
+
+ val incomeTotals: ArrayList = ArrayList()
+
+ for (month in 0..11) {
+
+ val incomes = incomeRepository.getAllIncomesByMonthAndYear(month, year).first()
+
+ val total: Double = try {
+ if (incomes.isEmpty()) {
+ 0.0
+ } else {
+ val tempIncome =
+ incomeRepository.applyFilterByPaymentMethods(paymentMethods, incomes)
+
+ tempIncome.sumOf { it.income }
+ }
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ 0.0
+ }
+
+ incomeTotals.add(MonthAndTotalSum(month, total))
+ }
+
+ return incomeTotals
+ }
+
+ private suspend fun getExpenseTotalAfterApplyingPaymentMethodFilter(
+ datePairForExpense: Pair,
+ categoryKeys: List,
+ paymentMethodKeys: List
+ ): Double {
+
+ val expenses = expenseRepository.getExpenseByCategoryKeysAndDateRange(
+ categoryKeys,
+ datePairForExpense.first,
+ datePairForExpense.second + Constants.ONE_DAY_MILLISECONDS
+ ).first()
+
+ return try {
+ if (expenses.isEmpty()) {
+ 0.0
+ } else {
+ val tempExpense = expenseRepository.applyExpenseFilterByPaymentMethods(
+ paymentMethodKeys,
+ expenses
+ )
+
+ tempExpense.sumOf { it.amount }
+ }
+ } catch (e: Exception) {
+ 0.0
+ }
+
+ }
+
+ private var _budgetExpense = MutableLiveData>()
+ val budgetExpense: LiveData> get() = _budgetExpense
+
+ fun getEveryDayExpenseData(categoryKey: String, month: Int, year: Int) {
+
+ viewModelScope.launch {
+
+ val startingMillisOfDays =
+ WorkingWithDateAndTime.getStartMillisecondOfAllDaysInMonth(month, year)
+
+ val dataList = ArrayList()
+
+ for (i in 1..startingMillisOfDays.size) {
+
+ val dayMillis = startingMillisOfDays[i - 1]
+
+ Log.d(
+ TAG,
+ "getEveryDayExpenseData: $i ----> ${dayMillis.first} ,,,,, ${dayMillis.second}"
+ )
+
+ val expenseAmount = try {
+ expenseRepository.getTotalExpenseAmountByCategoryKeyAndDateRange(
+ categoryKey, dayMillis.first, dayMillis.second
+ ).first()
+ } catch (e: java.lang.NullPointerException) {
+ 0.0
+ }
+
+ val barEntry = BarEntry(i.toFloat(), expenseAmount.toFloat())
+ dataList.add(barEntry)
+ }
+
+ _budgetExpense.value = dataList
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/BudgetViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/BudgetViewModel.kt
new file mode 100644
index 00000000..dba389ee
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/BudgetViewModel.kt
@@ -0,0 +1,427 @@
+package com.rohitthebest.manageyourrenters.ui.viewModels
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import com.rohitthebest.manageyourrenters.database.model.Budget
+import com.rohitthebest.manageyourrenters.database.model.Expense
+import com.rohitthebest.manageyourrenters.database.model.ExpenseCategory
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BUDGETS
+import com.rohitthebest.manageyourrenters.repositories.BudgetRepository
+import com.rohitthebest.manageyourrenters.repositories.ExpenseCategoryRepository
+import com.rohitthebest.manageyourrenters.repositories.ExpenseRepository
+import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import com.rohitthebest.manageyourrenters.utils.compareObjects
+import com.rohitthebest.manageyourrenters.utils.convertStringListToJSON
+import com.rohitthebest.manageyourrenters.utils.deleteAllDocumentsUsingKeyFromFirestore
+import com.rohitthebest.manageyourrenters.utils.deleteDocumentFromFireStore
+import com.rohitthebest.manageyourrenters.utils.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.updateDocumentOnFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadDocumentToFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadListOfDataToFireStore
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+private const val TAG = "BudgetViewModel"
+
+@HiltViewModel
+class BudgetViewModel @Inject constructor(
+ app: Application,
+ private val budgetRepository: BudgetRepository,
+ private val expenseCategoryRepository: ExpenseCategoryRepository,
+ private val expenseRepository: ExpenseRepository
+) : AndroidViewModel(app) {
+
+ fun insertBudget(budget: Budget) = viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ if (context.isInternetAvailable()) {
+
+ budget.isSynced = true
+
+ uploadDocumentToFireStore(
+ context, BUDGETS, budget.key
+ )
+ } else {
+
+ budget.isSynced = false
+ }
+
+ budgetRepository.insertBudget(budget)
+ }
+
+ fun updateBudget(oldValue: Budget, newValue: Budget) = viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ if (Functions.isInternetAvailable(context)) {
+
+ newValue.isSynced = true
+
+ if (!oldValue.isSynced) {
+ uploadDocumentToFireStore(
+ context,
+ BUDGETS,
+ newValue.key
+ )
+ } else {
+
+ val map = compareObjects(
+ oldData = oldValue,
+ newData = newValue,
+ notToCompareFields = listOf("modified")
+ )
+
+ Log.d(TAG, "updateBudget: difference: $map")
+
+ if (map.isNotEmpty()) {
+
+ map["modified"] = newValue.modified
+
+ updateDocumentOnFireStore(
+ context,
+ map,
+ BUDGETS,
+ oldValue.key
+ )
+ }
+ }
+ } else {
+ newValue.isSynced = false
+ }
+
+ budgetRepository.updateBudget(newValue)
+ }
+
+ fun deleteBudget(budget: Budget) = viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ deleteDocumentFromFireStore(
+ context,
+ BUDGETS,
+ budget.key
+ )
+
+ budgetRepository.deleteBudget(budget)
+ }
+
+ fun deleteAllBudgets() = viewModelScope.launch {
+ budgetRepository.deleteAllBudgets()
+ }
+
+ private val _allBudgetListByMonthAndYear = MutableLiveData>()
+ val allBudgetListByMonthAndYear: LiveData> get() = _allBudgetListByMonthAndYear
+
+ fun getAllBudgetsByMonthAndYear(
+ month: Int = WorkingWithDateAndTime.getCurrentMonth(),
+ year: Int = WorkingWithDateAndTime.getCurrentYear(),
+ selectedPaymentMethods: List = emptyList()
+ ) {
+
+ viewModelScope.launch {
+
+ var allBudgets =
+ budgetRepository.getAllBudgetsByMonthAndYear(month, year).first()
+
+ val allBudgetExpenseCategoryKeys = allBudgets.map { it.expenseCategoryKey }
+ var expenseCategories =
+ expenseCategoryRepository.getExpenseCategoriesByKey(allBudgetExpenseCategoryKeys)
+ .first()
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ month, year
+ )
+
+ allBudgets = allBudgets.sortedBy { it.expenseCategoryKey }
+ expenseCategories = expenseCategories.sortedBy { it.key }
+
+ addOtherDetailsToBudget(
+ allBudgets,
+ expenseCategories,
+ datePairForExpense,
+ selectedPaymentMethods
+ )
+
+ _allBudgetListByMonthAndYear.value = allBudgets
+ }
+ }
+
+ fun getAllExpenseCategoryOfBudgetsByMonthAndYear(
+ month: Int = WorkingWithDateAndTime.getCurrentMonth(),
+ year: Int = WorkingWithDateAndTime.getCurrentYear()
+ ) = budgetRepository.getExpenseCategoryKeysOfAllBudgetsByMonthAndYear(month, year).asLiveData()
+
+
+ private val _allExpenseCategoryAsBudgets = MutableLiveData>()
+ val allExpenseCategoryAsBudgets: LiveData> get() = _allExpenseCategoryAsBudgets
+
+ fun getAllExpenseCategoryAsBudget(month: Int, year: Int) {
+
+ viewModelScope.launch {
+
+ // get all expense categories
+ val allExpenseCategories =
+ ArrayList(expenseCategoryRepository.getAllExpenseCategories().first())
+
+ // get all budgets for the selected month and year
+ val allBudgetsAlreadyAddedByMonthAndYear =
+ budgetRepository.getAllBudgetsByMonthAndYear(month, year).first()
+
+
+ // get all the expense category key for which budget is added
+ val budgetCategoryKeys =
+ allBudgetsAlreadyAddedByMonthAndYear.map { it.expenseCategoryKey }
+
+ val categoriesWithBudgetAlreadyAdded = allExpenseCategories.filter {
+ budgetCategoryKeys.contains(it.key)
+ }
+
+ // remove all the expense categories for which budget is already added
+ allExpenseCategories.removeAll(categoriesWithBudgetAlreadyAdded.toSet())
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ month, year
+ )
+
+ addOtherDetailsToBudget(
+ allBudgetsAlreadyAddedByMonthAndYear.sortedBy { it.expenseCategoryKey },
+ categoriesWithBudgetAlreadyAdded.sortedBy { it.key },
+ datePairForExpense
+ )
+
+ val expenseCategoryAsBudget: ArrayList =
+ ArrayList(allBudgetsAlreadyAddedByMonthAndYear)
+
+ allExpenseCategories.forEach { category ->
+
+ val budget = Budget()
+ budget.apply {
+ this.currentExpenseAmount = 0.0
+ this.budgetLimit = 0.0
+ this.month = month
+ this.year = year
+ this.expenseCategoryKey = category.key
+ this.monthYearString = this.generateMonthYearString()
+ this.categoryName = category.categoryName
+ this.categoryImageUrl = category.imageUrl ?: ""
+ this.modified = System.currentTimeMillis()
+ }
+ expenseCategoryAsBudget.add(budget)
+ }
+
+ _allExpenseCategoryAsBudgets.value =
+ expenseCategoryAsBudget.sortedBy { it.modified }
+ }
+ }
+
+ private suspend fun addOtherDetailsToBudget(
+ allBudgets: List,
+ expenseCategories: List,
+ datePairForExpense: Pair,
+ selectedPaymentMethods: List = emptyList()
+ ) {
+
+ allBudgets.forEachIndexed { index, budget ->
+
+ budget.categoryName = expenseCategories[index].categoryName
+ budget.categoryImageUrl = expenseCategories[index].imageUrl ?: ""
+
+ val currentExpense = calculateCurrentExpense(
+ datePairForExpense,
+ expenseCategories[index].key,
+ selectedPaymentMethods
+ )
+
+ Log.d(
+ TAG,
+ "addOtherDetailsToBudget: currentExpense (${budget.categoryName}): $currentExpense"
+ )
+
+ budget.currentExpenseAmount = currentExpense
+ }
+
+ }
+
+ private fun applyExpenseFilterByPaymentMethods(
+ paymentMethodKeys: List,
+ expenses: List
+ ): List {
+
+ return expenseRepository.applyExpenseFilterByPaymentMethods(paymentMethodKeys, expenses)
+ }
+
+
+ fun getTheOldestSavedBudgetYear() = budgetRepository.getTheOldestSavedBudgetYear().asLiveData()
+
+ fun getBudgetByKey(budgetKey: String) = budgetRepository.getBudgetByKey(budgetKey).asLiveData()
+
+ private var _budgetByKey = MutableLiveData()
+ val budgetByKey: LiveData get() = _budgetByKey
+
+ fun getBudgetByKeyWithACategoryAndExpenseDetails(
+ budgetKey: String,
+ month: Int,
+ year: Int,
+ selectedPaymentMethods: List = emptyList()
+ ) {
+
+ viewModelScope.launch {
+
+ val budget = budgetRepository.getBudgetByKey(budgetKey).first()
+ val category =
+ expenseCategoryRepository.getExpenseCategoryByKey(budget.expenseCategoryKey).first()
+
+ budget.categoryName = category.categoryName
+ budget.categoryImageUrl = category.imageUrl ?: ""
+
+ val datePairForExpense =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ month, year
+ )
+
+ val currentExpense: Double = calculateCurrentExpense(
+ datePairForExpense,
+ category.key,
+ selectedPaymentMethods
+ )
+
+ budget.currentExpenseAmount = currentExpense
+
+ _budgetByKey.value = budget
+ }
+ }
+
+ private suspend fun calculateCurrentExpense(
+ datePairForExpense: Pair,
+ categoryKey: String,
+ selectedPaymentMethods: List
+ ): Double {
+
+ return try {
+ if (selectedPaymentMethods.isEmpty()) {
+ expenseRepository.getTotalExpenseAmountByCategoryKeyAndDateRange(
+ categoryKey,
+ datePairForExpense.first,
+ datePairForExpense.second + Constants.ONE_DAY_MILLISECONDS
+ ).first()
+ } else {
+ val tempExpense = expenseRepository.getExpenseByDateRangeAndExpenseCategoryKey(
+ categoryKey,
+ datePairForExpense.first,
+ datePairForExpense.second + Constants.ONE_DAY_MILLISECONDS
+ ).first()
+
+ val expenseAfterPaymentMethodFilter = applyExpenseFilterByPaymentMethods(
+ selectedPaymentMethods,
+ tempExpense
+ )
+ expenseAfterPaymentMethodFilter.sumOf { it.amount }
+ }
+ } catch (e: Exception) {
+ 0.0
+ }
+
+ }
+
+
+ fun getTotalBudgetByMonthAndYear(month: Int, year: Int) =
+ budgetRepository.getTotalBudgetByMonthAndYear(month, year).asLiveData()
+
+ fun getAllBudgetMonthAndYearForWhichBudgetIsAdded() =
+ budgetRepository.getAllBudgetMonthAndYearForWhichBudgetIsAdded().asLiveData()
+
+ fun isAnyBudgetAddedForThisMonthAndYear(monthYearString: String) =
+ budgetRepository.isAnyBudgetAddedForThisMonthAndYear(monthYearString).asLiveData()
+
+ fun duplicateBudgetOfPreviouslyAddedBudgetMonth(
+ isBudgetAddedForSelectedMonth: Boolean,
+ selectedMonthAndYear: Pair,
+ monthAndYearFromWhichBudgetHasToBeDuplicated: String
+ ) {
+
+ viewModelScope.launch {
+
+ if (isBudgetAddedForSelectedMonth) {
+
+ // delete all the budgets already added for this month
+ deleteAllBudgetsByMonthAndYear(
+ selectedMonthAndYear.first,
+ selectedMonthAndYear.second
+ )
+
+ delay(120)
+ }
+
+ // getting all the budgets
+ val allBudgetsOfMonthFromWhichBudgetHasToBeDuplicated =
+ budgetRepository.getAllBudgetsByMonthAndYearString(
+ monthAndYearFromWhichBudgetHasToBeDuplicated
+ ).first()
+
+ val duplicatedBudgetList =
+ allBudgetsOfMonthFromWhichBudgetHasToBeDuplicated.toMutableList()
+
+
+ duplicatedBudgetList.forEach { budget ->
+
+ budget.key = Functions.generateKey("_${Functions.getUid()}")
+ budget.created = System.currentTimeMillis()
+ budget.modified = System.currentTimeMillis()
+ budget.month = selectedMonthAndYear.first
+ budget.year = selectedMonthAndYear.second
+ budget.monthYearString = budget.generateMonthYearString()
+ budget.isSynced = true
+ }
+
+ insertAllBudget(duplicatedBudgetList)
+ }
+ }
+
+ fun deleteAllBudgetsByMonthAndYear(month: Int, year: Int) {
+
+ viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ val budgetKeys = budgetRepository.getKeysByMonthAndYear(month, year)
+
+ if (budgetKeys.isNotEmpty()) {
+
+ deleteAllDocumentsUsingKeyFromFirestore(
+ context,
+ BUDGETS,
+ convertStringListToJSON(budgetKeys)
+ )
+ }
+
+ budgetRepository.deleteBudgetsByMonthAndYear(month, year)
+ }
+
+ }
+
+ private fun insertAllBudget(budgets: List) = viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ uploadListOfDataToFireStore(
+ context,
+ BUDGETS,
+ convertStringListToJSON(budgets.map { it.key })
+ )
+
+ budgetRepository.insertAllBudget(budgets)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseCategoryViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseCategoryViewModel.kt
index 701c938f..67b81da7 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseCategoryViewModel.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseCategoryViewModel.kt
@@ -4,20 +4,37 @@ import android.app.Application
import android.content.Context
import android.os.Parcelable
import android.util.Log
-import androidx.lifecycle.*
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
import com.rohitthebest.manageyourrenters.database.model.ExpenseCategory
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.BUDGETS
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EXPENSES
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EXPENSE_CATEGORIES
+import com.rohitthebest.manageyourrenters.repositories.BudgetRepository
import com.rohitthebest.manageyourrenters.repositories.ExpenseCategoryRepository
import com.rohitthebest.manageyourrenters.repositories.ExpenseRepository
+import com.rohitthebest.manageyourrenters.repositories.IncomeRepository
import com.rohitthebest.manageyourrenters.repositories.MonthlyPaymentRepository
-import com.rohitthebest.manageyourrenters.utils.*
+import com.rohitthebest.manageyourrenters.utils.Functions
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.compareExpenseCategoryModel
+import com.rohitthebest.manageyourrenters.utils.convertStringListToJSON
+import com.rohitthebest.manageyourrenters.utils.deleteAllDocumentsUsingKeyFromFirestore
+import com.rohitthebest.manageyourrenters.utils.deleteDocumentFromFireStore
+import com.rohitthebest.manageyourrenters.utils.deleteFileFromFirebaseStorage
+import com.rohitthebest.manageyourrenters.utils.isValid
+import com.rohitthebest.manageyourrenters.utils.updateDocumentOnFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadDocumentToFireStore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
+import kotlin.collections.set
private const val TAG = "ExpenseCategoryViewMode"
@@ -26,6 +43,8 @@ class ExpenseCategoryViewModel @Inject constructor(
app: Application,
private val expenseCategoryRepository: ExpenseCategoryRepository,
private val expenseRepository: ExpenseRepository,
+ private val budgetRepository: BudgetRepository,
+ private val incomeRepository: IncomeRepository,
private val monthlyPaymentRepository: MonthlyPaymentRepository,
private val state: SavedStateHandle
) : AndroidViewModel(app) {
@@ -68,13 +87,13 @@ class ExpenseCategoryViewModel @Inject constructor(
} else {
- expenseCategory.isSynced = false
- }
+ expenseCategory.isSynced = false
+ }
- expenseCategoryRepository.insertExpenseCategory(expenseCategory)
+ expenseCategoryRepository.insertExpenseCategory(expenseCategory)
- Functions.showToast(context, "Expense Category saved")
- }
+ Functions.showToast(context, "Expense Category saved")
+ }
fun updateExpenseCategory(
oldValue: ExpenseCategory,
@@ -140,6 +159,7 @@ class ExpenseCategoryViewModel @Inject constructor(
}
val expenseKeys = expenseRepository.getKeysByExpenseCategoryKey(expenseCategory.key)
+ val budgetKeys = budgetRepository.getKeysByExpenseCategoryKey(expenseCategory.key)
unlinkMonthlyPaymentsIfAny(context, expenseKeys)
@@ -152,9 +172,19 @@ class ExpenseCategoryViewModel @Inject constructor(
)
}
+ if (budgetKeys.isNotEmpty()) {
+
+ deleteAllDocumentsUsingKeyFromFirestore(
+ context,
+ BUDGETS,
+ convertStringListToJSON(budgetKeys)
+ )
+ }
+
}
expenseRepository.deleteExpenseByExpenseCategoryKey(expenseCategory.key)
+ budgetRepository.deleteBudgetsByExpenseCategoryKey(expenseCategory.key)
expenseCategoryRepository.deleteExpenseCategory(expenseCategory)
}
@@ -202,6 +232,8 @@ class ExpenseCategoryViewModel @Inject constructor(
expenseCategoryRepository.deleteAllExpenseCategories()
expenseRepository.deleteAllExpenses()
+ budgetRepository.deleteAllBudgets()
+ incomeRepository.deleteAllIncomes()
}
fun getExpenseCategoryByKey(key: String) =
@@ -209,4 +241,6 @@ class ExpenseCategoryViewModel @Inject constructor(
fun getAllExpenseCategories() = expenseCategoryRepository.getAllExpenseCategories().asLiveData()
+ fun getAllExpenseCategoriesByLimit(limit: Int) =
+ expenseCategoryRepository.getAllExpenseCategoriesByLimit(limit).asLiveData()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseGraphDataViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseGraphDataViewModel.kt
new file mode 100644
index 00000000..588a0290
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseGraphDataViewModel.kt
@@ -0,0 +1,300 @@
+package com.rohitthebest.manageyourrenters.ui.viewModels
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import com.rohitthebest.manageyourrenters.R
+import com.rohitthebest.manageyourrenters.data.ExpenseCategoryAndTheirTotalExpenseAmounts
+import com.rohitthebest.manageyourrenters.others.Constants
+import com.rohitthebest.manageyourrenters.repositories.ExpenseRepository
+import com.rohitthebest.manageyourrenters.utils.WorkingWithDateAndTime
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import java.util.Calendar
+import javax.inject.Inject
+
+@HiltViewModel
+class ExpenseGraphDataViewModel @Inject constructor(
+ app: Application,
+ private val expenseRepository: ExpenseRepository
+) : AndroidViewModel(app) {
+
+ /*
+ This variable will be used for storing the data in Pair as
+ first: ExpenseCategoryAndTotalAmount
+ second: Total of all the totalAmounts
+ */
+ private val _expenseGraphData =
+ MutableLiveData, Double>>()
+ val expenseGraphData: LiveData, Double>> get() = _expenseGraphData
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryNames(paymentMethodKeys: List = emptyList()) {
+
+ viewModelScope.launch {
+
+ val expenseCategoriesWithTheirAmounts =
+ try {
+ if (paymentMethodKeys.isEmpty()) {
+ expenseRepository.getTotalExpenseAmountsWithTheirExpenseCategoryKeys()
+ .first()
+ } else {
+ getExpenseCategoriesAndTheirAmountsAfterFilteringByPaymentMethods(
+ paymentMethodKeys
+ )
+ }
+ } catch (e: Exception) {
+ emptyList()
+ }
+
+ val totalAmount = if (expenseCategoriesWithTheirAmounts.isNotEmpty()) {
+ try {
+ if (paymentMethodKeys.isEmpty()) {
+ expenseRepository.getTotalExpenseAmount().first()
+ } else {
+ expenseCategoriesWithTheirAmounts.sumOf { it.totalAmount }
+ }
+ } catch (e: NullPointerException) {
+ 0.0
+ }
+ } else {
+ 0.0
+ }
+
+ setValueForExpenseGraphDataPair(expenseCategoriesWithTheirAmounts, totalAmount)
+ }
+ }
+
+ private suspend fun getExpenseCategoriesAndTheirAmountsAfterFilteringByPaymentMethods(
+ paymentMethodKeys: List
+ ): List {
+
+ val expenses = expenseRepository.getAllExpenses().first()
+
+ val filteredList =
+ expenseRepository.applyExpenseFilterByPaymentMethods(paymentMethodKeys, expenses)
+
+ return expenseRepository.getTotalExpenseAmountsWithTheirExpenseCategoryKeysByListOfExpenseKeys(
+ filteredList.map { expense -> expense.key }
+ ).first()
+ }
+
+ fun getTotalExpenseAmountsWithTheirExpenseCategoryNamesByDateRange(
+ date1: Long,
+ date2: Long,
+ paymentMethodKeys: List = emptyList()
+ ) {
+
+ viewModelScope.launch {
+ val expenseCategoriesWithTheirAmounts =
+ try {
+ if (paymentMethodKeys.isEmpty()) {
+ expenseRepository.getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRange(
+ date1,
+ date2
+ ).first()
+ } else {
+ getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRangeAndFilterByPaymentMethods(
+ date1,
+ date2,
+ paymentMethodKeys
+ )
+ }
+ } catch (e: Exception) {
+ emptyList()
+ }
+
+ val totalAmount = if (expenseCategoriesWithTheirAmounts.isNotEmpty()) {
+ try {
+ if (paymentMethodKeys.isEmpty()) {
+ expenseRepository.getTotalExpenseAmountByDateRange(
+ date1,
+ date2
+ ).first()
+ } else {
+ expenseCategoriesWithTheirAmounts.sumOf { it.totalAmount }
+ }
+ } catch (e: java.lang.NullPointerException) {
+ 0.0
+ }
+ } else {
+ 0.0
+ }
+
+ setValueForExpenseGraphDataPair(expenseCategoriesWithTheirAmounts, totalAmount)
+ }
+ }
+
+ private suspend fun getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRangeAndFilterByPaymentMethods(
+ date1: Long,
+ date2: Long,
+ paymentMethodKeys: List
+ ): List {
+
+ val expenses = expenseRepository.getExpensesByDateRange(date1, date2).first()
+
+ val filteredList =
+ expenseRepository.applyExpenseFilterByPaymentMethods(paymentMethodKeys, expenses)
+
+ return expenseRepository.getTotalExpenseAmountsWithTheirExpenseCategoryKeysByDateRangeAndByListOfExpenseKeys(
+ date1, date2, filteredList.map { expense -> expense.key }
+ ).first()
+ }
+
+ fun getTotalExpenseAmountWithTheirExpenseCategoryNamesForSelectedExpenseCategories(
+ selectedExpenseCategoryKeys: List
+ ) {
+
+ viewModelScope.launch {
+
+ val expenseCategoriesWithTheirAmounts =
+ try {
+ expenseRepository.getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategories(
+ selectedExpenseCategoryKeys
+ ).first()
+ } catch (e: Exception) {
+ emptyList()
+ }
+
+ val totalAmount =
+ try {
+ expenseRepository.getTotalExpenseByCategoryKeys(selectedExpenseCategoryKeys)
+ .first()
+ } catch (e: java.lang.NullPointerException) {
+ 0.0
+ }
+
+ setValueForExpenseGraphDataPair(expenseCategoriesWithTheirAmounts, totalAmount)
+ }
+ }
+
+ fun getTotalExpenseAmountWithTheirExpenseCategoryNamesForSelectedExpenseCategoriesByDateRange(
+ selectedExpenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ) {
+
+ viewModelScope.launch {
+
+ val expenseCategoriesWithTheirAmounts =
+ try {
+ expenseRepository.getTotalExpenseAmountsWithTheirExpenseCategoryKeysForSelectedExpenseCategoriesByDateRange(
+ selectedExpenseCategoryKeys, date1, date2
+ ).first()
+ } catch (e: Exception) {
+ emptyList()
+ }
+
+ val totalAmount =
+ try {
+ expenseRepository.getTotalExpenseByCategoryKeysAndDateRange(
+ selectedExpenseCategoryKeys,
+ date1,
+ date2
+ ).first()
+ } catch (e: NullPointerException) {
+ 0.0
+ }
+
+ setValueForExpenseGraphDataPair(expenseCategoriesWithTheirAmounts, totalAmount)
+ }
+ }
+
+
+ private fun setValueForExpenseGraphDataPair(
+ expenseCategoriesWithTheirAmounts: List,
+ totalAmount: Double
+ ) {
+
+ val expenseGraphDataPair: Pair, Double> =
+ Pair(expenseCategoriesWithTheirAmounts, totalAmount)
+
+ _expenseGraphData.value = expenseGraphDataPair
+ }
+
+ private val _expenseOfEachMonth = MutableLiveData>>()
+ val expenseOfEachMonth: LiveData>> get() = _expenseOfEachMonth
+
+ fun getExpensesOfAllMonthsOfYear(year: Int) {
+
+ val context = getApplication().applicationContext
+
+ viewModelScope.launch {
+
+ val listOfExpensesInEachMonth = ArrayList>()
+ val monthList = context.resources.getStringArray(R.array.months_short).asList()
+
+ for (i in 1..12) {
+
+ val calendar = Calendar.getInstance()
+ calendar.set(year, i - 1, 2)
+
+ val startAndEndDateInMillis =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonth(
+ calendar.timeInMillis
+ )
+
+ val amount = try {
+ expenseRepository.getTotalExpenseAmountByDateRange(
+ startAndEndDateInMillis.first,
+ startAndEndDateInMillis.second + Constants.ONE_DAY_MILLISECONDS
+ ).first()
+
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ 0.0
+ }
+
+ listOfExpensesInEachMonth.add(Pair(monthList[i - 1], amount))
+ }
+
+ _expenseOfEachMonth.value = listOfExpensesInEachMonth
+ }
+
+ }
+
+ fun getExpensesOfAllMonthsOfYearForSelectedCategory(
+ year: Int,
+ selectedExpenseCategoryKey: String
+ ) {
+
+ val context = getApplication().applicationContext
+
+ viewModelScope.launch {
+
+ val listOfExpensesInEachMonth = ArrayList>()
+ val monthList = context.resources.getStringArray(R.array.months_short).asList()
+
+ for (i in 1..12) {
+
+ val calendar = Calendar.getInstance()
+ calendar.set(year, i - 1, 2)
+
+ val startAndEndDateInMillis =
+ WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonth(
+ calendar.timeInMillis
+ )
+
+ val amount = try {
+ expenseRepository.getTotalExpenseAmountByCategoryKeyAndDateRange(
+ selectedExpenseCategoryKey,
+ startAndEndDateInMillis.first,
+ startAndEndDateInMillis.second + Constants.ONE_DAY_MILLISECONDS
+ ).first()
+
+ } catch (e: NullPointerException) {
+ e.printStackTrace()
+ 0.0
+ }
+
+ listOfExpensesInEachMonth.add(Pair(monthList[i - 1], amount))
+ }
+
+ _expenseOfEachMonth.value = listOfExpensesInEachMonth
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseViewModel.kt
index c449c0d0..aa44c731 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseViewModel.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/ExpenseViewModel.kt
@@ -3,22 +3,34 @@ package com.rohitthebest.manageyourrenters.ui.viewModels
import android.app.Application
import android.content.Context
import android.util.Log
-import androidx.lifecycle.*
-import com.rohitthebest.manageyourrenters.data.filter.*
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import com.rohitthebest.manageyourrenters.data.filter.ExpenseFilterDto
+import com.rohitthebest.manageyourrenters.data.filter.IntFilterOptions
+import com.rohitthebest.manageyourrenters.data.filter.SortFilter
+import com.rohitthebest.manageyourrenters.data.filter.SortOrder
+import com.rohitthebest.manageyourrenters.data.filter.StringFilterOptions
import com.rohitthebest.manageyourrenters.database.model.Expense
import com.rohitthebest.manageyourrenters.others.Constants
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.EXPENSES
import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.MONTHLY_PAYMENTS
import com.rohitthebest.manageyourrenters.repositories.ExpenseRepository
import com.rohitthebest.manageyourrenters.repositories.MonthlyPaymentRepository
-import com.rohitthebest.manageyourrenters.utils.*
+import com.rohitthebest.manageyourrenters.utils.Functions
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.compareExpenseModel
+import com.rohitthebest.manageyourrenters.utils.deleteDocumentFromFireStore
+import com.rohitthebest.manageyourrenters.utils.updateDocumentOnFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadDocumentToFireStore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import java.util.*
import java.util.regex.Pattern
import javax.inject.Inject
+import kotlin.collections.set
private const val TAG = "ExpenseViewModel"
@@ -233,52 +245,6 @@ class ExpenseViewModel @Inject constructor(
fun getExpensesByExpenseCategoryKey(expenseCategoryKey: String) =
expenseRepository.getExpensesByExpenseCategoryKey(expenseCategoryKey).asLiveData()
- private val _expenseOfEachMonth = MutableLiveData>(emptyList())
- val expenseOfEachMonth: LiveData> get() = _expenseOfEachMonth
-
- fun getExpensesOfAllMonthsOfYear(year: Int) {
-
- viewModelScope.launch {
-
- val calendars = ArrayList()
-
- val listOfExpensesInEachMonth = ArrayList()
-
- for (i in 1..12) {
-
- calendars.add(Calendar.getInstance())
- calendars[i - 1].set(year, i - 1, 2)
-
- val startAndEndDateInMillis =
- WorkingWithDateAndTime.getMillisecondsOfStartAndEndDayOfMonth(
- calendars[i - 1].timeInMillis
- )
-
- val amount = try {
- expenseRepository.getTotalExpenseAmountByDateRange(
- startAndEndDateInMillis.first,
- startAndEndDateInMillis.second + Constants.ONE_DAY_MILLISECONDS
- ).first()
-
- } catch (e: NullPointerException) {
- e.printStackTrace()
- 0.0
- }
-
- listOfExpensesInEachMonth.add(amount)
-
- Log.d(
- TAG,
- "getExpensesOfAllMonthsOfYear: Year : $year, Month : ${
- calendars[i - 1].get(Calendar.MONTH)
- }, Amount : $amount"
- )
- }
-
- _expenseOfEachMonth.value = listOfExpensesInEachMonth
- }
-
- }
// issue #78
private val _expensesByPaymentMethods = MutableLiveData>(emptyList())
@@ -390,25 +356,30 @@ class ExpenseViewModel @Inject constructor(
return mExpenses
}
- private fun applyFilterByPaymentMethods(
- paymentMethodKeys: List,
+ fun applyFilterByPaymentMethods(
+ paymentMethodKeys: List,
expenses: List
): List {
- val isOtherPaymentMethodKeyPresent =
- paymentMethodKeys.contains(Constants.PAYMENT_METHOD_OTHER_KEY)
-
- val resultExpenses = expenses.filter { expense ->
+ return expenseRepository.applyExpenseFilterByPaymentMethods(paymentMethodKeys, expenses)
+ }
- if (isOtherPaymentMethodKeyPresent) {
- // for other payment method, get all the expenses where payment methods is null as well as payment method is other
- expense.paymentMethods == null || expense.paymentMethods!!.any { it in paymentMethodKeys }
- } else {
- expense.paymentMethods != null && expense.paymentMethods!!.any { it in paymentMethodKeys }
- }
- }
+ fun getTotalExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ) = expenseRepository.getTotalExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys,
+ date1,
+ date2
+ ).asLiveData()
- return resultExpenses
- }
+ fun getExpenseByCategoryKeysAndDateRange(
+ expenseCategoryKeys: List,
+ date1: Long,
+ date2: Long
+ ) = expenseRepository.getExpenseByCategoryKeysAndDateRange(expenseCategoryKeys, date1, date2)
+ .asLiveData()
+ fun isAnyExpenseAdded() = expenseRepository.isAnyExpenseAdded().asLiveData()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/IncomeViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/IncomeViewModel.kt
new file mode 100644
index 00000000..9f589da9
--- /dev/null
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/IncomeViewModel.kt
@@ -0,0 +1,128 @@
+package com.rohitthebest.manageyourrenters.ui.viewModels
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import com.rohitthebest.manageyourrenters.database.model.Income
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
+import com.rohitthebest.manageyourrenters.repositories.IncomeRepository
+import com.rohitthebest.manageyourrenters.utils.Functions
+import com.rohitthebest.manageyourrenters.utils.compareObjects
+import com.rohitthebest.manageyourrenters.utils.deleteDocumentFromFireStore
+import com.rohitthebest.manageyourrenters.utils.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.updateDocumentOnFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadDocumentToFireStore
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class IncomeViewModel @Inject constructor(
+ app: Application,
+ private val repository: IncomeRepository
+) : AndroidViewModel(app) {
+
+ fun insertIncome(income: Income) = viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ if (context.isInternetAvailable()) {
+
+ income.isSynced = true
+
+ uploadDocumentToFireStore(
+ context, FirestoreCollectionsConstants.INCOMES, income.key
+ )
+ } else {
+
+ income.isSynced = false
+ }
+
+ repository.insertIncome(income)
+ }
+
+ fun insertAllIncome(incomes: List) = viewModelScope.launch {
+ repository.insertAllIncome(incomes)
+ }
+
+ fun updateIncome(oldValue: Income, newValue: Income) = viewModelScope.launch {
+ val context = getApplication().applicationContext
+
+ if (Functions.isInternetAvailable(context)) {
+
+ newValue.isSynced = true
+
+ if (!oldValue.isSynced) {
+ uploadDocumentToFireStore(
+ context,
+ FirestoreCollectionsConstants.INCOMES,
+ newValue.key
+ )
+ } else {
+
+ val map = compareObjects(
+ oldData = oldValue,
+ newData = newValue,
+ notToCompareFields = listOf("modified", "isSynced")
+ )
+
+ if (map.isNotEmpty()) {
+
+ map["modified"] = newValue.modified
+
+ updateDocumentOnFireStore(
+ context,
+ map,
+ FirestoreCollectionsConstants.INCOMES,
+ oldValue.key
+ )
+ }
+ }
+ } else {
+ newValue.isSynced = false
+ }
+
+ repository.updateIncome(newValue)
+ }
+
+ fun deleteIncome(income: Income) = viewModelScope.launch {
+
+ val context = getApplication().applicationContext
+
+ deleteDocumentFromFireStore(
+ context,
+ FirestoreCollectionsConstants.INCOMES,
+ income.key
+ )
+
+ repository.deleteIncome(income)
+ }
+
+ fun deleteAllIncomes() = viewModelScope.launch {
+ repository.deleteAllIncomes()
+ }
+
+ fun getAllIncomes() = repository.getAllIncomes().asLiveData()
+
+ fun getAllIncomesByMonthAndYear(month: Int, year: Int) = repository.getAllIncomesByMonthAndYear(
+ month, year
+ ).asLiveData()
+
+ fun getIncomeByKey(incomeKey: String) =
+ repository.getIncomeByKey(incomeKey = incomeKey).asLiveData()
+
+ fun getTotalIncomeAddedByMonthAndYear(month: Int, year: Int) =
+ repository.getTotalIncomeAddedByMonthAndYear(month, year).asLiveData()
+
+ fun getAllIncomeSources() = repository.getAllIncomeSources().asLiveData()
+
+ fun applyFilterByPaymentMethods(
+ paymentMethodKeys: List,
+ incomes: List
+ ): List {
+
+ return repository.applyFilterByPaymentMethods(paymentMethodKeys, incomes)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/RenterViewModel.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/RenterViewModel.kt
index 1f5d8ccf..ccd05dc0 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/RenterViewModel.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/ui/viewModels/RenterViewModel.kt
@@ -2,7 +2,12 @@ package com.rohitthebest.manageyourrenters.ui.viewModels
import android.app.Application
import android.os.Parcelable
-import androidx.lifecycle.*
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
import com.rohitthebest.manageyourrenters.R
import com.rohitthebest.manageyourrenters.data.DocumentType
import com.rohitthebest.manageyourrenters.data.SupportingDocument
@@ -15,13 +20,21 @@ import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants.R
import com.rohitthebest.manageyourrenters.repositories.DeletedRenterRepository
import com.rohitthebest.manageyourrenters.repositories.RenterPaymentRepository
import com.rohitthebest.manageyourrenters.repositories.RenterRepository
-import com.rohitthebest.manageyourrenters.utils.*
import com.rohitthebest.manageyourrenters.utils.Functions.Companion.isInternetAvailable
+import com.rohitthebest.manageyourrenters.utils.compareRenterModel
+import com.rohitthebest.manageyourrenters.utils.convertStringListToJSON
+import com.rohitthebest.manageyourrenters.utils.deleteAllDocumentsUsingKeyFromFirestore
+import com.rohitthebest.manageyourrenters.utils.deleteDocumentFromFireStore
+import com.rohitthebest.manageyourrenters.utils.deleteFileFromFirebaseStorage
+import com.rohitthebest.manageyourrenters.utils.updateDocumentOnFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadDocumentToFireStore
+import com.rohitthebest.manageyourrenters.utils.uploadFileToFirebaseCloudStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
+import kotlin.collections.set
private const val TAG = "RenterViewModel"
@@ -301,6 +314,9 @@ class RenterViewModel @Inject constructor(
}
}
+ val renterNameWithTheirDues: LiveData> =
+ repo.getRentersWithTheirDues().asLiveData()
+
fun getRentersWithTheirAmountPaidByDateCreated(
startDate: Long,
endDate: Long
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ExtensionFunctions.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ExtensionFunctions.kt
index cf279755..6f5d7bf2 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ExtensionFunctions.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ExtensionFunctions.kt
@@ -19,7 +19,11 @@ import android.text.TextWatcher
import android.text.style.StrikethroughSpan
import android.text.style.UnderlineSpan
import android.view.View
-import android.widget.*
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.EditText
+import android.widget.Spinner
+import android.widget.TextView
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
@@ -155,6 +159,28 @@ fun RecyclerView.changeVisibilityOfFABOnScrolled(fab: FloatingActionButton) {
})
}
+fun RecyclerView.changeVisibilityOfViewOnScrolled(view: View) {
+
+ this.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ super.onScrolled(recyclerView, dx, dy)
+
+ try {
+ if (dy > 0 && view.visibility == View.VISIBLE) {
+
+ view.hide()
+ } else if (dy < 0 && view.visibility != View.VISIBLE) {
+
+ view.show()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ })
+}
+
fun Double.format(digits: Int) = "%.${digits}f".format(this)
fun TextView.changeTextColor(context: Context, color: Int) {
@@ -182,6 +208,8 @@ fun String?.isValid(): Boolean {
&& this.trim().isNotBlank()
}
+fun String?.isNotValid() = !this.isValid()
+
inline fun EditText.onTextChangedListener(
crossinline onTextChanged: (s: CharSequence?) -> Unit
) {
@@ -209,17 +237,18 @@ inline fun EditText.onTextChangedListener(
inline fun showAlertDialogForDeletion(
context: Context,
crossinline positiveButtonListener: (DialogInterface) -> Unit,
- crossinline negativeButtonListener: (DialogInterface) -> Unit
+ crossinline negativeButtonListener: (DialogInterface) -> Unit,
+ message: String = ""
) {
MaterialAlertDialogBuilder(context)
- .setTitle("Are you sure?")
- .setMessage(context.getString(R.string.delete_warning_message))
- .setPositiveButton("Delete") { dialogInterface, _ ->
+ .setTitle(context.getString(R.string.are_you_sure))
+ .setMessage(if (message.isNotValid()) context.getString(R.string.delete_warning_message) else message)
+ .setPositiveButton(context.getString(R.string._delete)) { dialogInterface, _ ->
positiveButtonListener(dialogInterface)
}
- .setNegativeButton("Cancel") { dialogInterface, _ ->
+ .setNegativeButton(context.getText(R.string.cancel)) { dialogInterface, _ ->
negativeButtonListener(dialogInterface)
}
@@ -432,7 +461,7 @@ fun Bitmap.saveToStorage(context: Context, fileName: String): Uri? {
try {
- this.compress(Bitmap.CompressFormat.JPEG, 100, fout)
+ this.compress(Bitmap.CompressFormat.JPEG, 100, fout!!)
fout?.close()
} catch (e: java.lang.Exception) {
@@ -482,6 +511,7 @@ fun TextView.applyStyles(text: String, textStyle: String) {
text.replace("-critical", "")
)
}
+
text.startsWith("https") || text.startsWith("http") -> {
this.changeTextStyle(
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/FirebaseServiceHelper.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/FirebaseServiceHelper.kt
index 1ad6da5a..ef1658b0 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/FirebaseServiceHelper.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/FirebaseServiceHelper.kt
@@ -11,8 +11,16 @@ import com.rohitthebest.manageyourrenters.others.Constants
import com.rohitthebest.manageyourrenters.others.Constants.COLLECTION_KEY
import com.rohitthebest.manageyourrenters.others.Constants.DELETE_FILE_FROM_FIREBASE_KEY
import com.rohitthebest.manageyourrenters.others.Constants.DOCUMENT_KEY
+import com.rohitthebest.manageyourrenters.others.Constants.KEY_LIST_KEY
import com.rohitthebest.manageyourrenters.others.Constants.UPLOAD_DATA_KEY
-import com.rohitthebest.manageyourrenters.services.*
+import com.rohitthebest.manageyourrenters.others.FirestoreCollectionsConstants
+import com.rohitthebest.manageyourrenters.services.DeleteAllDocumentsService
+import com.rohitthebest.manageyourrenters.services.DeleteFileFromFirebaseStorageService
+import com.rohitthebest.manageyourrenters.services.DeleteService
+import com.rohitthebest.manageyourrenters.services.UpdateService
+import com.rohitthebest.manageyourrenters.services.UploadDocumentListToFireStoreService
+import com.rohitthebest.manageyourrenters.services.UploadFileToCloudStorageService
+import com.rohitthebest.manageyourrenters.services.UploadService
import kotlinx.coroutines.tasks.await
import kotlin.random.Random
@@ -63,7 +71,7 @@ fun updateDocumentOnFireStore(
foregroundService.putExtra(
Constants.UPDATE_DOCUMENT_MAP_KEY,
- map
+ map.convertToJsonString()
)
foregroundService.putExtra(
@@ -124,8 +132,14 @@ fun uploadListOfDataToFireStore(
uploadData
)
- ContextCompat.startForegroundService(context, foregroundService)
+ if (collection != FirestoreCollectionsConstants.PARTIAL_PAYMENTS) {
+ foregroundService.putExtra(
+ KEY_LIST_KEY,
+ uploadData
+ )
+ }
+ ContextCompat.startForegroundService(context, foregroundService)
}
fun deleteAllDocumentsUsingKeyFromFirestore(
@@ -142,7 +156,7 @@ fun deleteAllDocumentsUsingKeyFromFirestore(
)
foregroundService.putExtra(
- Constants.KEY_LIST_KEY,
+ KEY_LIST_KEY,
keyList
)
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/Functions.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/Functions.kt
index ea4ac3e0..11ca94a5 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/Functions.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/Functions.kt
@@ -68,7 +68,6 @@ class Functions {
private val mAuth = Firebase.auth
- private const val TAG = "Functions"
fun showToast(context: Context, message: Any?, duration: Int = Toast.LENGTH_SHORT) {
try {
Log.d(TAG, message.toString())
@@ -688,16 +687,19 @@ class Functions {
onMenuItemClicked(CustomDateRange.THIS_MONTH)
true
}
+
R.id.menu_date_range_this_week -> {
onMenuItemClicked(CustomDateRange.THIS_WEEK)
true
}
+
R.id.menu_date_range_previous_month -> {
onMenuItemClicked(CustomDateRange.PREVIOUS_MONTH)
true
}
+
R.id.menu_date_range_previous_week -> {
onMenuItemClicked(CustomDateRange.PREVIOUS_WEEK)
@@ -708,18 +710,22 @@ class Functions {
onMenuItemClicked(CustomDateRange.LAST_30_DAYS)
true
}
+
R.id.menu_date_range_last_7_days -> {
onMenuItemClicked(CustomDateRange.LAST_7_DAYS)
true
}
+
R.id.menu_date_range_last_365_days -> {
onMenuItemClicked(CustomDateRange.LAST_365_DAYS)
true
}
+
R.id.menu_date_range_all_time -> {
onMenuItemClicked(CustomDateRange.ALL_TIME)
true
}
+
R.id.menu_date_range_custom_range -> {
onMenuItemClicked(CustomDateRange.CUSTOM_DATE_RANGE)
true
@@ -978,12 +984,51 @@ class Functions {
)
} else {
- PendingIntent.getActivity(context, 0, notificationIntent, 0)
+ PendingIntent.getActivity(
+ context,
+ 0,
+ notificationIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
}
}
}
- }
+ fun getAppropriateBudgetSuggestionOrMessage(
+ context: Context,
+ progressInPercent: Int,
+ budgetExpense: Double,
+ budgetLimit: Double
+ ): String {
+
+ val message: List;
+
+ when {
+
+ (progressInPercent in 0..35) -> {
+ message =
+ context.resources.getStringArray(R.array.budget_start_messages).toList()
+ }
+
+ (progressInPercent in 36..68) -> {
+ message =
+ context.resources.getStringArray(R.array.budget_middle_messages).toList()
+ }
+ else -> {
+ message = if (budgetExpense > budgetLimit) {
+ context.resources.getStringArray(R.array.budget_overspent_messages).toList()
+ } else if (budgetExpense == budgetLimit) {
+ context.resources.getStringArray(R.array.budget_reachedLimit_messages)
+ .toList()
+ } else {
+ context.resources.getStringArray(R.array.budget_end_messages).toList()
+ }
+ }
+ }
+
+ return message[Random.nextInt(0, message.size - 1)]
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ModelComparator.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ModelComparator.kt
index a6139a37..09c51e35 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ModelComparator.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/ModelComparator.kt
@@ -1,12 +1,58 @@
package com.rohitthebest.manageyourrenters.utils
-import com.rohitthebest.manageyourrenters.database.model.*
+import com.rohitthebest.manageyourrenters.database.model.BorrowerPayment
+import com.rohitthebest.manageyourrenters.database.model.EMI
+import com.rohitthebest.manageyourrenters.database.model.EMIPayment
+import com.rohitthebest.manageyourrenters.database.model.Expense
+import com.rohitthebest.manageyourrenters.database.model.ExpenseCategory
+import com.rohitthebest.manageyourrenters.database.model.MonthlyPayment
+import com.rohitthebest.manageyourrenters.database.model.MonthlyPaymentCategory
+import com.rohitthebest.manageyourrenters.database.model.PaymentMethod
+import com.rohitthebest.manageyourrenters.database.model.Renter
+import com.rohitthebest.manageyourrenters.database.model.RenterPayment
/**
* This class helps to compare the main fields of
* old and new model and return a map of unmatched fields
*/
+// generic
+fun compareObjects(
+ oldData: T,
+ newData: T,
+ notToCompareFields: List
+): HashMap {
+
+ return try {
+
+ val result = HashMap()
+ val fields = oldData!!::class.java.declaredFields
+
+ for (field in fields) {
+
+ field.isAccessible = true
+
+ if (field.name != "key"
+ && field.name != "id"
+ && field.name != "uid"
+ && !notToCompareFields.contains(field.name)
+ ) {
+
+ val oldValue = field.get(oldData)
+ val newValue = field.get(newData)
+
+ if (oldValue != newValue) {
+ result[field.name] = newValue
+ }
+ }
+ }
+
+ return result
+ } catch (e: Exception) {
+ HashMap()
+ }
+}
+
fun compareBorrowerPaymentModel(
oldData: BorrowerPayment,
newData: BorrowerPayment
@@ -221,7 +267,6 @@ fun comparePaymentMethod(oldData: PaymentMethod, newData: PaymentMethod): HashMa
val map: HashMap = HashMap()
- if (oldData.isSynced != newData.isSynced) map["synced"] = newData.isSynced
if (oldData.paymentMethod != newData.paymentMethod) map["paymentMethod"] = newData.paymentMethod
return map
diff --git a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/WorkingWithDateAndTime.kt b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/WorkingWithDateAndTime.kt
index 62150358..2a6bae84 100644
--- a/app/src/main/java/com/rohitthebest/manageyourrenters/utils/WorkingWithDateAndTime.kt
+++ b/app/src/main/java/com/rohitthebest/manageyourrenters/utils/WorkingWithDateAndTime.kt
@@ -5,7 +5,10 @@ import android.util.Log
import java.sql.Timestamp
import java.text.ParseException
import java.text.SimpleDateFormat
+import java.time.YearMonth
import java.util.*
+import java.util.regex.Matcher
+import java.util.regex.Pattern
@SuppressLint("SimpleDateFormat")
object WorkingWithDateAndTime {
@@ -290,4 +293,156 @@ object WorkingWithDateAndTime {
return Pair(firstCal.timeInMillis, lastCal.timeInMillis)
}
+ fun getMonthAndYearString(month: Int, year: Int): String {
+
+ return "${month}_${year}"
+ }
+
+ fun extractMonthAndYearFromMonthAndYearString(monthYearString: String): Pair {
+
+ return try {
+
+ val pattern = "(\\d+)_(\\d+)"
+ val regex: Pattern = Pattern.compile(pattern)
+ val matcher: Matcher = regex.matcher(monthYearString)
+
+ if (matcher.find()) {
+
+ val month = matcher.group(1)?.toInt()
+ val year = matcher.group(2)?.toInt()
+
+ return if (month != null && year != null) {
+
+ Pair(month, year)
+ } else {
+ Pair(0, 0)
+ }
+
+ } else {
+ return Pair(0, 0)
+ }
+
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Pair(0, 0)
+ }
+
+ }
+
+
+ fun getNextMonth(selectedMonth: Int): Int {
+
+ val cal = Calendar.getInstance()
+ cal.set(Calendar.MONTH, selectedMonth)
+ cal.add(Calendar.MONTH, 1)
+ return cal.get(Calendar.MONTH)
+ }
+
+ fun getPreviousMonth(selectedMonth: Int): Int {
+
+ val cal = Calendar.getInstance()
+ cal.set(Calendar.MONTH, selectedMonth)
+ cal.add(Calendar.MONTH, -1)
+ return cal.get(Calendar.MONTH)
+ }
+
+ fun getMillisecondsOfStartAndEndDayOfMonthForGivenMonthAndYear(
+ month: Int,
+ year: Int
+ ): Pair {
+
+ val calendar = Calendar.getInstance()
+ calendar.set(year, month, 5)
+ val dateInMillis = calendar.timeInMillis
+ return getMillisecondsOfStartAndEndDayOfMonth(dateInMillis)
+ }
+
+ /**
+ * @param month - month (0-11)
+ * @param year - year (Ex: 2023)
+ *
+ * @return - Returns the number of days in a give month
+ */
+ fun getNumberOfDaysInMonth(month: Int, year: Int): Int {
+
+ val yearMonth = YearMonth.of(year, month + 1)
+ return yearMonth.lengthOfMonth()
+ }
+
+ /**
+ * @param month - month (0-11)
+ * @param year - year (Ex: 2023)
+ *
+ * @return - Returns the number of days left in the month (Int)
+ */
+ fun getNumberOfDaysLeftInAnyMonth(month: Int, year: Int): Int {
+
+ val currentYear = getCurrentYear()
+ val currentMonth = getCurrentMonth()
+ val numberOfDaysInMonth = getNumberOfDaysInMonth(month, year)
+
+ if (year < currentYear) return 0
+ if (year > currentYear) return numberOfDaysInMonth
+
+ if (month < currentMonth) return 0
+ if (month > currentMonth) return numberOfDaysInMonth
+
+ // year = current year and month = current month
+ val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
+
+ return numberOfDaysInMonth - currentDay
+ }
+
+ /**
+ * @param month - month (0-11)
+ * @param year - year (Ex: 2023)
+ *
+ * @return - Returns list of Pair object for which the
+ * first value is the start milliseconds of the day (example : if the date is 20-09-2023, then it's value will the milliseconds of (20-09-2023 00:00:00)),
+ * second value is the end milliseconds of the day (example : if the date is 20-09-2023, then it's value will the milliseconds of (20-09-2023 24:00:00))
+ */
+ fun getStartMillisecondOfAllDaysInMonth(month: Int, year: Int): List> {
+
+ val numberOfDays = getNumberOfDaysInMonth(month, year)
+
+ val startCalendar = Calendar.getInstance()
+ val endCalendar = Calendar.getInstance()
+
+ val startingMillisOfDays = ArrayList>()
+
+ for (i in 1..numberOfDays) {
+
+ startCalendar.set(year, month, i, 0, 0)
+ endCalendar.set(year, month, i, 24, 0)
+ startingMillisOfDays.add(Pair(startCalendar.time.time, endCalendar.time.time))
+ }
+
+ return startingMillisOfDays
+ }
+
+ /**
+ * @param month - month (0-11)
+ * @param year - year (Ex: 2023)
+ *
+ * @return - Returns the list of a pair object for which the first value is the int value of the DAY_OF_WEEK and the second value is String value i.e., human readable form
+ */
+ fun getDayOfWeeksForEntireMonth(month: Int, year: Int): List> {
+
+ val calendar = Calendar.getInstance()
+ val numberOfDays = getNumberOfDaysInMonth(month, year)
+
+ val daysList = ArrayList>()
+
+ for (i in 1..numberOfDays) {
+
+ calendar.set(year, month, i)
+ val dayOfWeekInt = calendar.get(Calendar.DAY_OF_WEEK)
+ val dayOfWeekString = SimpleDateFormat("EEEE").format(calendar.time)
+
+ daysList.add(Pair(dayOfWeekInt, dayOfWeekString))
+ }
+
+ return daysList;
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/baseline_check_24.xml b/app/src/main/res/drawable/baseline_check_24.xml
new file mode 100644
index 00000000..5623ef04
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_check_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_check_circle_green_24.xml b/app/src/main/res/drawable/baseline_check_circle_green_24.xml
new file mode 100644
index 00000000..a7ef1b65
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_check_circle_green_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_check_circle_red_24.xml b/app/src/main/res/drawable/baseline_check_circle_red_24.xml
new file mode 100644
index 00000000..64a88dff
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_check_circle_red_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_check_circle_yellow_24.xml b/app/src/main/res/drawable/baseline_check_circle_yellow_24.xml
new file mode 100644
index 00000000..c2ddee04
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_check_circle_yellow_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_filter_list_colored_24.xml b/app/src/main/res/drawable/baseline_filter_list_colored_24.xml
index b78e8dea..d635aa5c 100644
--- a/app/src/main/res/drawable/baseline_filter_list_colored_24.xml
+++ b/app/src/main/res/drawable/baseline_filter_list_colored_24.xml
@@ -1,7 +1,7 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/custom_progress_bar_2.xml b/app/src/main/res/drawable/custom_progress_bar_2.xml
new file mode 100644
index 00000000..f021c766
--- /dev/null
+++ b/app/src/main/res/drawable/custom_progress_bar_2.xml
@@ -0,0 +1,30 @@
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_bar_chart_24_white.xml b/app/src/main/res/drawable/ic_baseline_bar_chart_24_white.xml
new file mode 100644
index 00000000..136c1e6d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_bar_chart_24_white.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_white_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_white_24.xml
new file mode 100644
index 00000000..27e7c33a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_white_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_more_vert_white_24.xml b/app/src/main/res/drawable/ic_baseline_more_vert_white_24.xml
new file mode 100644
index 00000000..b29b2aef
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_more_vert_white_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/adapter_budget.xml b/app/src/main/res/layout/adapter_budget.xml
new file mode 100644
index 00000000..f6df6fd3
--- /dev/null
+++ b/app/src/main/res/layout/adapter_budget.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_income_list_item.xml b/app/src/main/res/layout/adapter_income_list_item.xml
new file mode 100644
index 00000000..a6490c4e
--- /dev/null
+++ b/app/src/main/res/layout/adapter_income_list_item.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_select_month_and_year.xml b/app/src/main/res/layout/adapter_select_month_and_year.xml
new file mode 100644
index 00000000..c9124383
--- /dev/null
+++ b/app/src/main/res/layout/adapter_select_month_and_year.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_set_budget_expense_category.xml b/app/src/main/res/layout/adapter_set_budget_expense_category.xml
new file mode 100644
index 00000000..4d73a622
--- /dev/null
+++ b/app/src/main/res/layout/adapter_set_budget_expense_category.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/budget_overview_layout.xml b/app/src/main/res/layout/budget_overview_layout.xml
new file mode 100644
index 00000000..beb2f130
--- /dev/null
+++ b/app/src/main/res/layout/budget_overview_layout.xml
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_month_and_year.xml b/app/src/main/res/layout/dialog_month_and_year.xml
new file mode 100644
index 00000000..a3b87f6c
--- /dev/null
+++ b/app/src/main/res/layout/dialog_month_and_year.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_payment_method_selector.xml b/app/src/main/res/layout/dialog_payment_method_selector.xml
new file mode 100644
index 00000000..447155fa
--- /dev/null
+++ b/app/src/main/res/layout/dialog_payment_method_selector.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/edit_text_bottom_sheet_layout.xml b/app/src/main/res/layout/edit_text_bottom_sheet_layout.xml
index 84363a22..28fb1bb0 100644
--- a/app/src/main/res/layout/edit_text_bottom_sheet_layout.xml
+++ b/app/src/main/res/layout/edit_text_bottom_sheet_layout.xml
@@ -41,6 +41,7 @@
app:errorEnabled="true">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_income_bootmsheet.xml b/app/src/main/res/layout/fragment_add_income_bootmsheet.xml
new file mode 100644
index 00000000..096802c2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_income_bootmsheet.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_budget.xml b/app/src/main/res/layout/fragment_budget.xml
new file mode 100644
index 00000000..2eb15400
--- /dev/null
+++ b/app/src/main/res/layout/fragment_budget.xml
@@ -0,0 +1,460 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_budget_and_income_graph.xml b/app/src/main/res/layout/fragment_budget_and_income_graph.xml
new file mode 100644
index 00000000..8e19c2e5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_budget_and_income_graph.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_budget_overview.xml b/app/src/main/res/layout/fragment_budget_overview.xml
new file mode 100644
index 00000000..b98a7590
--- /dev/null
+++ b/app/src/main/res/layout/fragment_budget_overview.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_choose_month_and_year.xml b/app/src/main/res/layout/fragment_choose_month_and_year.xml
new file mode 100644
index 00000000..8211e8b9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_choose_month_and_year.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_graph.xml b/app/src/main/res/layout/fragment_graph.xml
index 001ec0a3..c95ca949 100644
--- a/app/src/main/res/layout/fragment_graph.xml
+++ b/app/src/main/res/layout/fragment_graph.xml
@@ -111,4 +111,20 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_income.xml b/app/src/main/res/layout/fragment_income.xml
new file mode 100644
index 00000000..572e1c8b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_income.xml
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_monthly_graph.xml b/app/src/main/res/layout/fragment_monthly_graph.xml
index 59479a89..0dbfd4d6 100644
--- a/app/src/main/res/layout/fragment_monthly_graph.xml
+++ b/app/src/main/res/layout/fragment_monthly_graph.xml
@@ -24,24 +24,81 @@
-
-
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="16dp"
+ android:layout_weight="1"
+ app:cardCornerRadius="10dp"
+ app:cardElevation="10dp"
+ app:strokeColor="@color/color_green"
+ app:strokeWidth="2dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ app:layout_constraintTop_toBottomOf="@+id/ll" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/budget_overview_item_menu.xml b/app/src/main/res/menu/budget_overview_item_menu.xml
new file mode 100644
index 00000000..2854602e
--- /dev/null
+++ b/app/src/main/res/menu/budget_overview_item_menu.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/expense_category_menu.xml b/app/src/main/res/menu/expense_category_menu.xml
index 52bd998a..5104c880 100644
--- a/app/src/main/res/menu/expense_category_menu.xml
+++ b/app/src/main/res/menu/expense_category_menu.xml
@@ -23,6 +23,9 @@
android:id="@+id/menu_item_payment_methods_expense"
android:title="@string/payment_methods"
app:showAsAction="never" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/graph_menu.xml b/app/src/main/res/menu/graph_menu.xml
index 3e6027ce..559e020e 100644
--- a/app/src/main/res/menu/graph_menu.xml
+++ b/app/src/main/res/menu/graph_menu.xml
@@ -2,33 +2,52 @@
+
+
-
+
+
+
-
+ -
-
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/house_renters_home_menu.xml b/app/src/main/res/menu/house_renters_home_menu.xml
index b124ac85..1e4cb406 100644
--- a/app/src/main/res/menu/house_renters_home_menu.xml
+++ b/app/src/main/res/menu/house_renters_home_menu.xml
@@ -18,6 +18,11 @@
android:title="@string/show_deleted_renters"
app:showAsAction="never" />
+
+
-
diff --git a/app/src/main/res/menu/income_item_menu.xml b/app/src/main/res/menu/income_item_menu.xml
new file mode 100644
index 00000000..9d7cef71
--- /dev/null
+++ b/app/src/main/res/menu/income_item_menu.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_add_budget.xml b/app/src/main/res/menu/menu_add_budget.xml
new file mode 100644
index 00000000..9088aa1d
--- /dev/null
+++ b/app/src/main/res/menu/menu_add_budget.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_add_budget_item.xml b/app/src/main/res/menu/menu_add_budget_item.xml
new file mode 100644
index 00000000..2160f421
--- /dev/null
+++ b/app/src/main/res/menu/menu_add_budget_item.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_income_and_budget_overview.xml b/app/src/main/res/menu/menu_income_and_budget_overview.xml
new file mode 100644
index 00000000..60ee6ee2
--- /dev/null
+++ b/app/src/main/res/menu/menu_income_and_budget_overview.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/track_money_navigation.xml b/app/src/main/res/navigation/track_money_navigation.xml
index 425ab7a4..01a711c4 100644
--- a/app/src/main/res/navigation/track_money_navigation.xml
+++ b/app/src/main/res/navigation/track_money_navigation.xml
@@ -156,6 +156,13 @@
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/fade_out" />
+
+ tools:layout="@layout/fragment_monthly_graph">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 23fd3907..25ed49f6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -16,9 +16,10 @@
#B37AEEFF
- #F44336
+ #B62A20
#3D8C0A
#FF6D00
+ #FFC107
#a5c1ff
#212121
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fcabc21a..0795bf4a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -175,6 +175,7 @@
PUT
DELETE
Click (+) button to add Expense Category.\nExample : Shopping, Snacks, etc…
+ Please add expense categories first to add budget limit
Add expense category
Expense Category
Add Image
@@ -206,7 +207,7 @@
Show All Expenses
expense shortcut is disabled for now
house renters shortcut is disabled
- Deep analyze mode
+ Category compare graph
Add Remove Expense category selection
Add Remove payment method selection
clear selection fab
@@ -376,6 +377,104 @@
Click on download for downloading the image/GIF.
Using latest version %1$s
+ Budget and Income
+ previous budget date
+ next budget date
+ Savings
+ 100000000000
+ Expense
+ Budget
+ Income
+ Add Budget
+ Click on ADD BUDGET button to add budgets for this month
+ %1$s\u0020,\u0020%2$s
+ Spent\u0020%1$s\u0020from\u0020%2$s
+ %1$s\u0020per day (%2$s day(s) left)
+ Enter budget limit
+ Add budget limit
+ Budget limit should be greater than 0
+ Income should be greater than 0
+ Remove limit
+ Budget will be removed for this month
+ Ok
+ Source
+ No month or year received for which income needs to be added
+ Click on + button to add the income
+ Add income
+ Next month
+ Linked payment methods
+ Edit Income
+ Modify budgets
+ Total: %1$s
+ Please add limit for this category
+ Syncing values now…
+ Copy previous month\'s budget
+ Replace the current budget limit for this month
+ No budget limit added yet for any month
+ Delete all budget for this month
+ All the budget limits for this month will be reset.
+ 100%
+ Insights
+ Income vs Budget vs Expense
+ Savings Growth
+ Renter dues
+ Revenue - All Time
+ Minimum two categories needs to be selected
+ No Data available!!!
+ Month
+ Monthly expense for the year : %1$s
+ Monthly expense for the year: %1$s and Category: %2$s
+ Category graph
+ Select categories spinner
+ Monthly category expense graph
+ Screenshot
+ Screenshot saved to phone storage
+ You still have %1$s left in your budget
+ Limit reached!
+ Overspent!
+
+
+
+ - Plenty of Room to Spare
+ - Keep Going, You\'re On Track!
+ - Remember to Stick to Your Plan
+ - You Have Control Over Your Finances
+ - You Have Control Over Your Finances
+
+
+
+
+ - You\'re Doing Well, But Be Cautious
+ - Keep an Eye on Your Remaining Budget
+ - Maintain the Balance
+
+
+
+
+
+ - Alert: You\'re Approaching Your Budget Limit!
+ - Last Few Dollars Left – Spend Wisely
+ - You\'re Almost at Your Budget Limit
+ - You\'re Nearing the Limit
+
+
+
+
+
+ - Time to Review Your Expenses
+ - Review Your Budgeting Strategy
+
+
+
+
+
+ - Oops! You\'ve Exceeded Your Budget
+ - Learn from This Month and Plan Better Next Time
+ - Avoid Unnecessary Expenses
+ - Budgeting Is a Learning Process
+ - Seek Ways to Increase Income
+
+