diff --git a/app/build.gradle b/app/build.gradle
index 394041f..a2f394d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -21,7 +21,7 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// buildConfigField "String", "API_URL", "\"http://192.168.86.163:8080/\""
// buildConfigField "String", "API_URL", "\"http://10.0.2.2:8080/\""
- buildConfigField "String", "API_URL", "\"https://budget-api.intra.wbrawner.com/\""
+ buildConfigField "String", "API_URL", "\"https://api.twigs.brawner.dev/\""
}
buildTypes {
release {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b91e2ae..91610a9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,7 +12,8 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
- tools:ignore="GoogleAppIndexingWarning">
+ tools:ignore="GoogleAppIndexingWarning"
+ tools:targetApi="n">
-
+
diff --git a/app/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt b/app/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt
index ce75019..27f29a5 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt
@@ -46,6 +46,7 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
R.id.loginFragment
}
navController.navigate(navId)
+ if (navId == R.id.mainActivity) finish()
}
}
}
diff --git a/app/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt b/app/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt
index 672586b..c00f5af 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt
@@ -1,7 +1,6 @@
package com.wbrawner.budget.ui.auth
-import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -9,10 +8,10 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
+import androidx.navigation.fragment.findNavController
import com.wbrawner.budget.AllowanceApplication
import com.wbrawner.budget.R
import com.wbrawner.budget.di.BudgetViewModelFactory
-import com.wbrawner.budget.ui.MainActivity
import com.wbrawner.budget.ui.SplashViewModel
import com.wbrawner.budget.ui.ensureNotEmpty
import com.wbrawner.budget.ui.show
@@ -49,7 +48,7 @@ class LoginFragment : Fragment(), CoroutineScope {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- viewModel.isLoading.observe(this, Observer { isLoading ->
+ viewModel.isLoading.observe(viewLifecycleOwner, Observer { isLoading ->
formPrompt.show(!isLoading)
usernameContainer.show(!isLoading)
passwordContainer.show(!isLoading)
@@ -69,7 +68,8 @@ class LoginFragment : Fragment(), CoroutineScope {
try {
val user = viewModel.login(username.text.toString(), password.text.toString())
(requireActivity().application as AllowanceApplication).currentUser = user
- startActivity(Intent(requireContext().applicationContext, MainActivity::class.java))
+ findNavController().navigate(R.id.mainActivity)
+ activity?.finish()
} catch (e: Exception) {
username.error = "Invalid username/password"
password.error = "Invalid username/password"
diff --git a/app/src/main/java/com/wbrawner/budget/ui/base/ListWithAddButtonFragment.kt b/app/src/main/java/com/wbrawner/budget/ui/base/ListWithAddButtonFragment.kt
index 4c8c91a..492a9ea 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/base/ListWithAddButtonFragment.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/base/ListWithAddButtonFragment.kt
@@ -21,7 +21,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
-abstract class ListWithAddButtonFragment : Fragment(), CoroutineScope {
+abstract class ListWithAddButtonFragment : Fragment(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main
@Inject
lateinit var viewModelFactory: BudgetViewModelFactory
@@ -30,7 +30,7 @@ abstract class ListWithAddButtonFragment : Fragment(), Cor
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- viewModel = ViewModelProviders.of(this, viewModelFactory).get(viewModelClass)
+ viewModel = ViewModelProvider(this, viewModelFactory).get(viewModelClass)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
@@ -40,7 +40,7 @@ abstract class ListWithAddButtonFragment : Fragment(), Cor
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- viewModel.isLoading.observe(this, Observer {
+ viewModel.isLoading.observe(viewLifecycleOwner, Observer {
view.findViewById(R.id.progressBar)?.show(it)
})
recyclerView.layoutManager = LinearLayoutManager(view.context)
@@ -61,14 +61,14 @@ abstract class ListWithAddButtonFragment : Fragment(), Cor
if (view == null) return@launch
val (items, constructors) = loadItems()
if (items.isEmpty()) {
- recyclerView.adapter = null
- recyclerView.visibility = View.GONE
- noItemsTextView.setText(noItemsStringRes)
- noItemsTextView.visibility = View.VISIBLE
+ recyclerView?.adapter = null
+ recyclerView?.visibility = View.GONE
+ noItemsTextView?.setText(noItemsStringRes)
+ noItemsTextView?.visibility = View.VISIBLE
} else {
- recyclerView.adapter = BindableAdapter(items, constructors)
- recyclerView.visibility = View.VISIBLE
- noItemsTextView.visibility = View.GONE
+ recyclerView?.adapter = BindableAdapter(items, constructors)
+ recyclerView?.visibility = View.VISIBLE
+ noItemsTextView?.visibility = View.GONE
}
}
}
@@ -88,7 +88,7 @@ abstract class ListWithAddButtonFragment : Fragment(), Cor
abstract fun addItem()
- abstract suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>>
+ abstract suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>>
}
abstract class LoadingViewModel : ViewModel() {
diff --git a/app/src/main/java/com/wbrawner/budget/ui/base/RecyclerViewInterfaces.kt b/app/src/main/java/com/wbrawner/budget/ui/base/RecyclerViewInterfaces.kt
index 77857a2..76573fa 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/base/RecyclerViewInterfaces.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/base/RecyclerViewInterfaces.kt
@@ -10,12 +10,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
-class BindableAdapter(
- private val items: List,
- private val constructors: Map BindableViewHolder>
-) : RecyclerView.Adapter>() {
+class BindableAdapter(
+ private val items: List,
+ private val constructors: Map BindableViewHolder>
+) : RecyclerView.Adapter>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
- : BindableViewHolder = constructors[viewType]
+ : BindableViewHolder = constructors[viewType]
?.invoke(LayoutInflater.from(parent.context).inflate(viewType, parent, false))
?: throw IllegalStateException("Attempted to create ViewHolder without proper constructor provided")
@@ -23,11 +23,11 @@ class BindableAdapter(
override fun getItemViewType(position: Int): Int = items[position].viewType
- override fun onBindViewHolder(holder: BindableViewHolder, position: Int) {
+ override fun onBindViewHolder(holder: BindableViewHolder, position: Int) {
holder.onBind(items[position])
}
- override fun onViewRecycled(holder: BindableViewHolder) {
+ override fun onViewRecycled(holder: BindableViewHolder) {
holder.onUnbind()
}
diff --git a/app/src/main/java/com/wbrawner/budget/ui/budgets/BudgetListFragment.kt b/app/src/main/java/com/wbrawner/budget/ui/budgets/BudgetListFragment.kt
index 2f0fd8c..545f57d 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/budgets/BudgetListFragment.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/budgets/BudgetListFragment.kt
@@ -15,7 +15,7 @@ import com.wbrawner.budget.ui.base.BindableState
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
import kotlinx.coroutines.CoroutineScope
-class BudgetListFragment : ListWithAddButtonFragment(), CoroutineScope {
+class BudgetListFragment : ListWithAddButtonFragment(), CoroutineScope {
override val noItemsStringRes: Int = R.string.overview_no_data
override val viewModelClass: Class = BudgetViewModel::class.java
@@ -24,12 +24,19 @@ class BudgetListFragment : ListWithAddButtonFragment(), Corouti
super.onCreate(savedInstanceState)
}
- override suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>> {
+ override suspend fun loadItems(): Pair, Map BudgetViewHolder>> {
val budgetItems = viewModel.getBudgets().map { BudgetState(it) }
- val navController = findNavController()
+
return Pair(
budgetItems,
- mapOf(BUDGET_VIEW to { v -> BudgetViewHolder(v, navController) as BindableAdapter.BindableViewHolder })
+ mapOf(BUDGET_VIEW to { v ->
+ BudgetViewHolder(v) { _, budget ->
+ val bundle = Bundle().apply {
+ putLong(EXTRA_BUDGET_ID, budget.id!!)
+ }
+ findNavController().navigate(R.id.categoryListFragment, bundle)
+ }
+ })
)
}
@@ -46,7 +53,7 @@ class BudgetState(val budget: Budget) : BindableState {
class BudgetViewHolder(
itemView: View,
- private val navController: NavController
+ private val budgetClickListener: (View, Budget) -> Unit
) : BindableAdapter.BindableViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.budgetName)
private val description: TextView = itemView.findViewById(R.id.budgetDescription)
@@ -62,10 +69,7 @@ class BudgetViewHolder(
description.text = budget.description
}
itemView.setOnClickListener {
- val bundle = Bundle().apply {
- putLong(EXTRA_BUDGET_ID, budget.id!!)
- }
- navController.navigate(R.id.categoryListFragment, bundle)
+ budgetClickListener(it, budget)
}
}
}
diff --git a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryFragment.kt b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryFragment.kt
index abaeb84..020dd12 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryFragment.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryFragment.kt
@@ -21,7 +21,7 @@ import com.wbrawner.budget.ui.transactions.TransactionViewHolder
/**
* A simple [Fragment] subclass.
*/
-class CategoryFragment : ListWithAddButtonFragment() {
+class CategoryFragment : ListWithAddButtonFragment() {
override val noItemsStringRes: Int = R.string.transactions_no_data
override val viewModelClass: Class = CategoryViewModel::class.java
@@ -31,7 +31,7 @@ class CategoryFragment : ListWithAddButtonFragment() {
setHasOptionsMenu(true)
}
- override suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>> {
+ override suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>> {
val categoryId = arguments?.getLong(EXTRA_CATEGORY_ID)
if (categoryId == null) {
findNavController().navigateUp()
@@ -40,12 +40,12 @@ class CategoryFragment : ListWithAddButtonFragment() {
val category = viewModel.getCategory(categoryId)
activity?.title = category.title
// TODO: Add category details here as well
- val items = ArrayList()
+ val items = ArrayList()
items.addAll(viewModel.getTransactions(categoryId).map { TransactionState(it) })
return Pair(
items,
mapOf(TRANSACTION_VIEW to { v ->
- TransactionViewHolder(v, findNavController()) as BindableAdapter.BindableViewHolder
+ TransactionViewHolder(v, findNavController())
})
)
}
diff --git a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListFragment.kt b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListFragment.kt
index 5eae273..e63d5d7 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListFragment.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListFragment.kt
@@ -18,7 +18,7 @@ import com.wbrawner.budget.ui.base.BindableState
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
import kotlinx.coroutines.launch
-class CategoryListFragment : ListWithAddButtonFragment() {
+class CategoryListFragment : ListWithAddButtonFragment() {
override val noItemsStringRes: Int = R.string.categories_no_data
override val viewModelClass: Class = CategoryViewModel::class.java
@@ -27,7 +27,7 @@ class CategoryListFragment : ListWithAddButtonFragment() {
super.onCreate(savedInstanceState)
}
- override suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>> {
+ override suspend fun loadItems(): Pair, Map CategoryViewHolder>> {
val budgetId = arguments?.getLong(EXTRA_BUDGET_ID)
if (budgetId == null) {
findNavController().navigateUp()
@@ -38,7 +38,7 @@ class CategoryListFragment : ListWithAddButtonFragment() {
return Pair(
viewModel.getCategories(budgetId).map { CategoryState(it) },
mapOf(CATEGORY_VIEW to { v ->
- CategoryViewHolder(v, viewModel, findNavController()) as BindableAdapter.BindableViewHolder
+ CategoryViewHolder(v, viewModel, findNavController())
})
)
}
diff --git a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListViewModel.kt b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListViewModel.kt
index f80bd5e..b540b16 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListViewModel.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryListViewModel.kt
@@ -17,7 +17,7 @@ class CategoryListViewModel @Inject constructor(
suspend fun getCategory(id: Long): Category = categoryRepo.findById(id)
suspend fun getCategories(budgetId: Long? = null): Collection =
- categoryRepo.findAll(budgetId)
+ categoryRepo.findAll(budgetId?.let { arrayOf(it) })
suspend fun saveCategory(category: Category) = if (category.id == null) categoryRepo.create(category)
else categoryRepo.update(category)
diff --git a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryViewModel.kt b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryViewModel.kt
index 07171ca..21cfd00 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryViewModel.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/categories/CategoryViewModel.kt
@@ -35,7 +35,7 @@ class CategoryViewModel @Inject constructor(
}
suspend fun getCategories(budgetId: Long? = null): Collection = showLoader {
- categoryRepo.findAll(budgetId)
+ categoryRepo.findAll(budgetId?.let { arrayOf(it) })
}
suspend fun saveCategory(category: Category) = showLoader {
diff --git a/app/src/main/java/com/wbrawner/budget/ui/transactions/AddEditTransactionViewModel.kt b/app/src/main/java/com/wbrawner/budget/ui/transactions/AddEditTransactionViewModel.kt
index e2973ba..a8d132d 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/transactions/AddEditTransactionViewModel.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/transactions/AddEditTransactionViewModel.kt
@@ -16,7 +16,7 @@ class AddEditTransactionViewModel @Inject constructor(
private val categoryRepository: CategoryRepository,
private val transactionRepository: TransactionRepository
) : ViewModel() {
- suspend fun getCategories(accountId: Long) = categoryRepository.findAll(accountId)
+ suspend fun getCategories(budgetId: Long) = categoryRepository.findAll(arrayOf(budgetId))
suspend fun getTransaction(id: Long) = transactionRepository.findById(id)
diff --git a/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListFragment.kt b/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListFragment.kt
index a220296..e0f3967 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListFragment.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListFragment.kt
@@ -3,8 +3,14 @@ package com.wbrawner.budget.ui.transactions
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
import android.view.View
+import android.widget.LinearLayout
import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
@@ -17,32 +23,71 @@ import com.wbrawner.budget.ui.EXTRA_TRANSACTION_ID
import com.wbrawner.budget.ui.base.BindableAdapter
import com.wbrawner.budget.ui.base.BindableState
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
+import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
+import java.util.*
-class TransactionListFragment : ListWithAddButtonFragment() {
+class TransactionListFragment : ListWithAddButtonFragment() {
override val viewModelClass: Class = TransactionListViewModel::class.java
override val noItemsStringRes: Int = R.string.transactions_no_data
override fun onCreate(savedInstanceState: Bundle?) {
(requireActivity().application as AllowanceApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.menu_transaction_list, menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId != R.id.filter) {
+ return super.onOptionsItemSelected(item)
+ }
+ val dialogView = LinearLayout(requireContext()).apply {
+ orientation = LinearLayout.VERTICAL
+ }
+ // TODO: Launch a Google Drive-style search/filter screen
+ AlertDialog.Builder(requireContext())
+ .setTitle("Filter Transactions")
+ .setPositiveButton(R.string.action_submit) { _, _ ->
+ launch {
+ loadItems(
+ arguments?.getLong(EXTRA_BUDGET_ID),
+ arguments?.getLong(EXTRA_CATEGORY_ID)
+ )
+ }
+ }
+ .setNegativeButton(R.string.action_cancel) { _, _ ->
+ // Do nothing
+ }
+ .create()
+ .show()
+ return true
}
override fun addItem() {
startActivity(Intent(activity, AddEditTransactionActivity::class.java))
}
- override suspend fun loadItems(): Pair, Map BindableAdapter.BindableViewHolder>> {
- return Pair(
- viewModel.getTransactions(
- arguments?.getLong(EXTRA_BUDGET_ID),
- arguments?.getLong(EXTRA_CATEGORY_ID)
- ).map { TransactionState(it) },
- mapOf(TRANSACTION_VIEW to { v ->
- TransactionViewHolder(v, findNavController()) as BindableAdapter.BindableViewHolder
- })
- )
- }
+ override suspend fun loadItems(): Pair, Map TransactionViewHolder>> = loadItems(
+ arguments?.getLong(EXTRA_BUDGET_ID),
+ arguments?.getLong(EXTRA_CATEGORY_ID)
+ )
+
+ private suspend fun loadItems(
+ budgetId: Long?,
+ categoryId: Long?,
+ from: Calendar? = null,
+ to: Calendar? = null
+ ): Pair, Map TransactionViewHolder>> = Pair(
+ viewModel.getTransactions(budgetId, categoryId, from, to).map { TransactionState(it) },
+ mapOf(TRANSACTION_VIEW to { v ->
+ TransactionViewHolder(v, findNavController())
+ })
+ )
+
companion object {
const val TAG_FRAGMENT = "transactions"
diff --git a/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListViewModel.kt b/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListViewModel.kt
index d39020d..c211f3b 100644
--- a/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListViewModel.kt
+++ b/app/src/main/java/com/wbrawner/budget/ui/transactions/TransactionListViewModel.kt
@@ -7,12 +7,18 @@ import com.wbrawner.budget.ui.base.LoadingViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
+import java.util.*
import javax.inject.Inject
class TransactionListViewModel @Inject constructor(private val transactionRepo: TransactionRepository) :
LoadingViewModel() {
- suspend fun getTransactions(budgetId: Long? = null, categoryId: Long? = null) = showLoader {
- transactionRepo.findAll(budgetId = budgetId, categoryId = categoryId)
+ suspend fun getTransactions(
+ budgetId: Long? = null,
+ categoryId: Long? = null,
+ start: Calendar? = null,
+ end: Calendar? = null
+ ) = showLoader {
+ transactionRepo.findAll(budgetId, categoryId, start, end)
}
}
diff --git a/app/src/main/res/drawable/ic_filter_list.xml b/app/src/main/res/drawable/ic_filter_list.xml
new file mode 100644
index 0000000..454bd7d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter_list.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index 3aabe64..a6ac098 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -9,10 +9,10 @@
+ android:fillColor="#1a1a1a" />
+ android:fillColor="#bfff00" />
diff --git a/app/src/main/res/drawable/ic_twigs_color.xml b/app/src/main/res/drawable/ic_twigs_color.xml
new file mode 100644
index 0000000..3aabe64
--- /dev/null
+++ b/app/src/main/res/drawable/ic_twigs_color.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_add_edit_category.xml b/app/src/main/res/layout/activity_add_edit_category.xml
index d9edeed..f0e40c0 100644
--- a/app/src/main/res/layout/activity_add_edit_category.xml
+++ b/app/src/main/res/layout/activity_add_edit_category.xml
@@ -22,18 +22,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar">
-
+ android:hint="@string/prompt_category_name">
+ android:hint="@string/prompt_category_amount">
+ android:text="@string/prompt_budget" />
-
+ android:layout_height="wrap_content" />
+
diff --git a/app/src/main/res/layout/fragment_list_with_add_button.xml b/app/src/main/res/layout/fragment_list_with_add_button.xml
index 796d3d7..3d0acd5 100644
--- a/app/src/main/res/layout/fragment_list_with_add_button.xml
+++ b/app/src/main/res/layout/fragment_list_with_add_button.xml
@@ -36,6 +36,7 @@
android:layout_margin="16dp"
android:clickable="true"
android:focusable="true"
+ app:backgroundTint="@color/colorPrimary"
app:srcCompat="@drawable/ic_add_white_24dp" />
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_transaction_list.xml b/app/src/main/res/menu/menu_transaction_list.xml
new file mode 100644
index 0000000..9d93432
--- /dev/null
+++ b/app/src/main/res/menu/menu_transaction_list.xml
@@ -0,0 +1,9 @@
+
+
\ 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 07efabf..10581c6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,8 +3,8 @@
#FFFFFFFF
#30d158
#34c759
- #bfff00
- #FF000000
+ #30d158
+ #1a1a1a
#388e3c
#d32f2f
#FFDADADA
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e0ec629..6e2cac3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -40,7 +40,6 @@
Login
Register
Forgot password?
- Account
Name
Description
Share with...
@@ -50,4 +49,8 @@
Edit Budget
Edit
Twigs Logo
+ Filter
+ Cancel
+ Month
+ Year
diff --git a/budgetlib/build.gradle b/budgetlib/build.gradle
index 311da5e..a66105f 100644
--- a/budgetlib/build.gradle
+++ b/budgetlib/build.gradle
@@ -29,7 +29,8 @@ dependencies {
// Retrofit
api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit"
implementation "com.squareup.retrofit2:converter-moshi:$rootProject.ext.retrofit"
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
+ implementation("com.squareup.okhttp3:logging-interceptor:4.7.2")
api "com.squareup.moshi:moshi:$rootProject.ext.moshi"
implementation "com.squareup.moshi:moshi-adapters:$rootProject.ext.moshi"
// Dagger
diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt
index 2cd4dc6..fe02a78 100644
--- a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt
+++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt
@@ -37,7 +37,7 @@ interface BudgetApiService {
// Categories
@GET("categories")
suspend fun getCategories(
- @Query("budgetId") budgetId: Long? = null,
+ @Query("budgetIds") budgetIds: Array? = null,
@Query("count") count: Int? = null,
@Query("page") page: Int? = null
): Collection
@@ -65,6 +65,8 @@ interface BudgetApiService {
suspend fun getTransactions(
@Query("budgetId") budgetId: Long? = null,
@Query("categoryId") categoryId: Long? = null,
+ @Query("from") from: String? = null,
+ @Query("to") to: String? = null,
@Query("count") count: Int? = null,
@Query("page") page: Int? = null
): Collection
diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt
index 1f37488..3ae30d5 100644
--- a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt
+++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt
@@ -12,12 +12,14 @@ import com.wbrawner.budget.lib.repository.NetworkBudgetRepository
import com.wbrawner.budget.lib.repository.NetworkCategoryRepository
import com.wbrawner.budget.lib.repository.NetworkTransactionRepository
import com.wbrawner.budget.lib.repository.NetworkUserRepository
+import com.wbrawner.budgetlib.BuildConfig
import dagger.Module
import dagger.Provides
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.nio.charset.Charset
@@ -34,10 +36,10 @@ class NetworkModule {
@Provides
fun provideCookieJar(sharedPreferences: SharedPreferences): CookieJar {
return object : CookieJar {
- override fun saveFromResponse(url: HttpUrl, cookies: MutableList) {
+ override fun saveFromResponse(url: HttpUrl, cookies: List) {
sharedPreferences.edit()
.putString(
- url.host(),
+ url.host,
cookies.joinToString(separator = ",") {
Base64.encode(it.toString().toByteArray(), 0)
.toString(charset = Charset.forName("UTF-8"))
@@ -47,7 +49,7 @@ class NetworkModule {
}
override fun loadForRequest(url: HttpUrl): MutableList {
- return sharedPreferences.getString(url.host(), "")
+ return sharedPreferences.getString(url.host, "")
?.split(",")
?.mapNotNull {
Cookie.parse(
@@ -64,7 +66,13 @@ class NetworkModule {
@Provides
fun provideOkHttpClient(cookieJar: CookieJar): OkHttpClient = OkHttpClient.Builder()
.cookieJar(cookieJar)
- // TODO: Add Gander interceptor
+ .apply {
+ if (BuildConfig.DEBUG)
+ this.addInterceptor(
+ HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT)
+ .setLevel(HttpLoggingInterceptor.Level.BODY)
+ )
+ }
.build()
@Provides
diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkCategoryRepository.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkCategoryRepository.kt
index 413a8d8..5ffd9a8 100644
--- a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkCategoryRepository.kt
+++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkCategoryRepository.kt
@@ -7,7 +7,7 @@ import javax.inject.Inject
class NetworkCategoryRepository @Inject constructor(private val apiService: BudgetApiService) : CategoryRepository {
override suspend fun create(newItem: Category): Category = apiService.newCategory(newItem)
- override suspend fun findAll(accountId: Long?): Collection = apiService.getCategories(accountId).sortedBy { it.title }
+ override suspend fun findAll(budgetIds: Array?): Collection = apiService.getCategories(budgetIds).sortedBy { it.title }
override suspend fun findAll(): Collection = findAll(null)
diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkTransactionRepository.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkTransactionRepository.kt
index 800e937..1af76f3 100644
--- a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkTransactionRepository.kt
+++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkTransactionRepository.kt
@@ -3,13 +3,33 @@ package com.wbrawner.budget.lib.repository
import com.wbrawner.budget.common.transaction.Transaction
import com.wbrawner.budget.common.transaction.TransactionRepository
import com.wbrawner.budget.lib.network.BudgetApiService
+import java.text.SimpleDateFormat
+import java.time.format.DateTimeFormatter
+import java.util.*
import javax.inject.Inject
class NetworkTransactionRepository @Inject constructor(private val apiService: BudgetApiService) : TransactionRepository {
+ private val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
+
override suspend fun create(newItem: Transaction): Transaction = apiService.newTransaction(newItem)
- override suspend fun findAll(budgetId: Long?, categoryId: Long?): Collection =
- apiService.getTransactions(budgetId, categoryId).sortedByDescending { it.date }
+ override suspend fun findAll(
+ budgetId: Long?,
+ categoryId: Long?,
+ start: Calendar?,
+ end: Calendar?
+ ): Collection = apiService.getTransactions(
+ budgetId,
+ categoryId,
+ start?.let {
+ it.timeZone = TimeZone.getTimeZone("UTC")
+ dateFormatter.format(it.time)
+ },
+ end?.let {
+ it.timeZone = TimeZone.getTimeZone("UTC")
+ dateFormatter.format(it.time)
+ }
+ )
override suspend fun findAll(): Collection = findAll(null)
diff --git a/common/src/main/java/com/wbrawner/budget/common/category/CategoryRepository.kt b/common/src/main/java/com/wbrawner/budget/common/category/CategoryRepository.kt
index 9b3d475..28563c4 100644
--- a/common/src/main/java/com/wbrawner/budget/common/category/CategoryRepository.kt
+++ b/common/src/main/java/com/wbrawner/budget/common/category/CategoryRepository.kt
@@ -3,6 +3,6 @@ package com.wbrawner.budget.common.category
import com.wbrawner.budget.common.Repository
interface CategoryRepository : Repository {
- suspend fun findAll(accountId: Long? = null): Collection
+ suspend fun findAll(budgetIds: Array? = null): Collection
suspend fun getBalance(id: Long): Long
}
\ No newline at end of file
diff --git a/common/src/main/java/com/wbrawner/budget/common/transaction/TransactionRepository.kt b/common/src/main/java/com/wbrawner/budget/common/transaction/TransactionRepository.kt
index 2cc75ce..30c2e0c 100644
--- a/common/src/main/java/com/wbrawner/budget/common/transaction/TransactionRepository.kt
+++ b/common/src/main/java/com/wbrawner/budget/common/transaction/TransactionRepository.kt
@@ -1,7 +1,13 @@
package com.wbrawner.budget.common.transaction
import com.wbrawner.budget.common.Repository
+import java.util.*
interface TransactionRepository : Repository {
- suspend fun findAll(budgetId: Long? = null, categoryId: Long? = null): Collection
+ suspend fun findAll(
+ budgetId: Long? = null,
+ categoryId: Long? = null,
+ start: Calendar? = null,
+ end: Calendar? = null
+ ): Collection
}
\ No newline at end of file