Clean up code and fix some discrepancies between API and local models/requests
This commit is contained in:
parent
623f65ea43
commit
7f3f1d82bc
29 changed files with 217 additions and 89 deletions
|
@ -21,7 +21,7 @@ android {
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
// buildConfigField "String", "API_URL", "\"http://192.168.86.163:8080/\""
|
// buildConfigField "String", "API_URL", "\"http://192.168.86.163:8080/\""
|
||||||
// buildConfigField "String", "API_URL", "\"http://10.0.2.2: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 {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
tools:ignore="GoogleAppIndexingWarning"
|
||||||
|
tools:targetApi="n">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.SplashActivity"
|
android:name=".ui.SplashActivity"
|
||||||
android:theme="@style/SplashTheme"
|
android:theme="@style/SplashTheme"
|
||||||
|
@ -27,7 +28,8 @@
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
|
|
||||||
<activity android:name=".ui.MainActivity" />
|
<activity android:name=".ui.MainActivity"
|
||||||
|
android:theme="@style/AppTheme" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.transactions.AddEditTransactionActivity"
|
android:name=".ui.transactions.AddEditTransactionActivity"
|
||||||
android:parentActivityName=".ui.MainActivity">
|
android:parentActivityName=".ui.MainActivity">
|
||||||
|
|
|
@ -46,6 +46,7 @@ class SplashActivity : AppCompatActivity(), CoroutineScope {
|
||||||
R.id.loginFragment
|
R.id.loginFragment
|
||||||
}
|
}
|
||||||
navController.navigate(navId)
|
navController.navigate(navId)
|
||||||
|
if (navId == R.id.mainActivity) finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.wbrawner.budget.ui.auth
|
package com.wbrawner.budget.ui.auth
|
||||||
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -9,10 +8,10 @@ import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.wbrawner.budget.AllowanceApplication
|
import com.wbrawner.budget.AllowanceApplication
|
||||||
import com.wbrawner.budget.R
|
import com.wbrawner.budget.R
|
||||||
import com.wbrawner.budget.di.BudgetViewModelFactory
|
import com.wbrawner.budget.di.BudgetViewModelFactory
|
||||||
import com.wbrawner.budget.ui.MainActivity
|
|
||||||
import com.wbrawner.budget.ui.SplashViewModel
|
import com.wbrawner.budget.ui.SplashViewModel
|
||||||
import com.wbrawner.budget.ui.ensureNotEmpty
|
import com.wbrawner.budget.ui.ensureNotEmpty
|
||||||
import com.wbrawner.budget.ui.show
|
import com.wbrawner.budget.ui.show
|
||||||
|
@ -49,7 +48,7 @@ class LoginFragment : Fragment(), CoroutineScope {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
viewModel.isLoading.observe(this, Observer { isLoading ->
|
viewModel.isLoading.observe(viewLifecycleOwner, Observer { isLoading ->
|
||||||
formPrompt.show(!isLoading)
|
formPrompt.show(!isLoading)
|
||||||
usernameContainer.show(!isLoading)
|
usernameContainer.show(!isLoading)
|
||||||
passwordContainer.show(!isLoading)
|
passwordContainer.show(!isLoading)
|
||||||
|
@ -69,7 +68,8 @@ class LoginFragment : Fragment(), CoroutineScope {
|
||||||
try {
|
try {
|
||||||
val user = viewModel.login(username.text.toString(), password.text.toString())
|
val user = viewModel.login(username.text.toString(), password.text.toString())
|
||||||
(requireActivity().application as AllowanceApplication).currentUser = user
|
(requireActivity().application as AllowanceApplication).currentUser = user
|
||||||
startActivity(Intent(requireContext().applicationContext, MainActivity::class.java))
|
findNavController().navigate(R.id.mainActivity)
|
||||||
|
activity?.finish()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
username.error = "Invalid username/password"
|
username.error = "Invalid username/password"
|
||||||
password.error = "Invalid username/password"
|
password.error = "Invalid username/password"
|
||||||
|
|
|
@ -21,7 +21,7 @@ import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
abstract class ListWithAddButtonFragment<T : LoadingViewModel> : Fragment(), CoroutineScope {
|
abstract class ListWithAddButtonFragment<T : LoadingViewModel, State: BindableState> : Fragment(), CoroutineScope {
|
||||||
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
override val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: BudgetViewModelFactory
|
lateinit var viewModelFactory: BudgetViewModelFactory
|
||||||
|
@ -30,7 +30,7 @@ abstract class ListWithAddButtonFragment<T : LoadingViewModel> : Fragment(), Cor
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory).get(viewModelClass)
|
viewModel = ViewModelProvider(this, viewModelFactory).get(viewModelClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
@ -40,7 +40,7 @@ abstract class ListWithAddButtonFragment<T : LoadingViewModel> : Fragment(), Cor
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
viewModel.isLoading.observe(this, Observer {
|
viewModel.isLoading.observe(viewLifecycleOwner, Observer {
|
||||||
view.findViewById<ProgressBar?>(R.id.progressBar)?.show(it)
|
view.findViewById<ProgressBar?>(R.id.progressBar)?.show(it)
|
||||||
})
|
})
|
||||||
recyclerView.layoutManager = LinearLayoutManager(view.context)
|
recyclerView.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
@ -61,14 +61,14 @@ abstract class ListWithAddButtonFragment<T : LoadingViewModel> : Fragment(), Cor
|
||||||
if (view == null) return@launch
|
if (view == null) return@launch
|
||||||
val (items, constructors) = loadItems()
|
val (items, constructors) = loadItems()
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
recyclerView.adapter = null
|
recyclerView?.adapter = null
|
||||||
recyclerView.visibility = View.GONE
|
recyclerView?.visibility = View.GONE
|
||||||
noItemsTextView.setText(noItemsStringRes)
|
noItemsTextView?.setText(noItemsStringRes)
|
||||||
noItemsTextView.visibility = View.VISIBLE
|
noItemsTextView?.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
recyclerView.adapter = BindableAdapter(items, constructors)
|
recyclerView?.adapter = BindableAdapter(items, constructors)
|
||||||
recyclerView.visibility = View.VISIBLE
|
recyclerView?.visibility = View.VISIBLE
|
||||||
noItemsTextView.visibility = View.GONE
|
noItemsTextView?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ abstract class ListWithAddButtonFragment<T : LoadingViewModel> : Fragment(), Cor
|
||||||
|
|
||||||
abstract fun addItem()
|
abstract fun addItem()
|
||||||
|
|
||||||
abstract suspend fun loadItems(): Pair<List<BindableState>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<in BindableState>>>
|
abstract suspend fun loadItems(): Pair<List<State>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<State>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class LoadingViewModel : ViewModel() {
|
abstract class LoadingViewModel : ViewModel() {
|
||||||
|
|
|
@ -10,12 +10,12 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class BindableAdapter(
|
class BindableAdapter<T : BindableState>(
|
||||||
private val items: List<BindableState>,
|
private val items: List<T>,
|
||||||
private val constructors: Map<Int, (view: View) -> BindableViewHolder<in BindableState>>
|
private val constructors: Map<Int, (view: View) -> BindableViewHolder<T>>
|
||||||
) : RecyclerView.Adapter<BindableAdapter.BindableViewHolder<in BindableState>>() {
|
) : RecyclerView.Adapter<BindableAdapter.BindableViewHolder<T>>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
|
||||||
: BindableViewHolder<in BindableState> = constructors[viewType]
|
: BindableViewHolder<T> = constructors[viewType]
|
||||||
?.invoke(LayoutInflater.from(parent.context).inflate(viewType, parent, false))
|
?.invoke(LayoutInflater.from(parent.context).inflate(viewType, parent, false))
|
||||||
?: throw IllegalStateException("Attempted to create ViewHolder without proper constructor provided")
|
?: 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 getItemViewType(position: Int): Int = items[position].viewType
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: BindableViewHolder<in BindableState>, position: Int) {
|
override fun onBindViewHolder(holder: BindableViewHolder<T>, position: Int) {
|
||||||
holder.onBind(items[position])
|
holder.onBind(items[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewRecycled(holder: BindableViewHolder<in BindableState>) {
|
override fun onViewRecycled(holder: BindableViewHolder<T>) {
|
||||||
holder.onUnbind()
|
holder.onUnbind()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import com.wbrawner.budget.ui.base.BindableState
|
||||||
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
|
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
class BudgetListFragment : ListWithAddButtonFragment<BudgetViewModel>(), CoroutineScope {
|
class BudgetListFragment : ListWithAddButtonFragment<BudgetViewModel, BudgetState>(), CoroutineScope {
|
||||||
override val noItemsStringRes: Int = R.string.overview_no_data
|
override val noItemsStringRes: Int = R.string.overview_no_data
|
||||||
override val viewModelClass: Class<BudgetViewModel> = BudgetViewModel::class.java
|
override val viewModelClass: Class<BudgetViewModel> = BudgetViewModel::class.java
|
||||||
|
|
||||||
|
@ -24,12 +24,19 @@ class BudgetListFragment : ListWithAddButtonFragment<BudgetViewModel>(), Corouti
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadItems(): Pair<List<BindableState>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<in BindableState>>> {
|
override suspend fun loadItems(): Pair<List<BudgetState>, Map<Int, (view: View) -> BudgetViewHolder>> {
|
||||||
val budgetItems = viewModel.getBudgets().map { BudgetState(it) }
|
val budgetItems = viewModel.getBudgets().map { BudgetState(it) }
|
||||||
val navController = findNavController()
|
|
||||||
return Pair(
|
return Pair(
|
||||||
budgetItems,
|
budgetItems,
|
||||||
mapOf(BUDGET_VIEW to { v -> BudgetViewHolder(v, navController) as BindableAdapter.BindableViewHolder<in BindableState> })
|
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(
|
class BudgetViewHolder(
|
||||||
itemView: View,
|
itemView: View,
|
||||||
private val navController: NavController
|
private val budgetClickListener: (View, Budget) -> Unit
|
||||||
) : BindableAdapter.BindableViewHolder<BudgetState>(itemView) {
|
) : BindableAdapter.BindableViewHolder<BudgetState>(itemView) {
|
||||||
private val name: TextView = itemView.findViewById(R.id.budgetName)
|
private val name: TextView = itemView.findViewById(R.id.budgetName)
|
||||||
private val description: TextView = itemView.findViewById(R.id.budgetDescription)
|
private val description: TextView = itemView.findViewById(R.id.budgetDescription)
|
||||||
|
@ -62,10 +69,7 @@ class BudgetViewHolder(
|
||||||
description.text = budget.description
|
description.text = budget.description
|
||||||
}
|
}
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
val bundle = Bundle().apply {
|
budgetClickListener(it, budget)
|
||||||
putLong(EXTRA_BUDGET_ID, budget.id!!)
|
|
||||||
}
|
|
||||||
navController.navigate(R.id.categoryListFragment, bundle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import com.wbrawner.budget.ui.transactions.TransactionViewHolder
|
||||||
/**
|
/**
|
||||||
* A simple [Fragment] subclass.
|
* A simple [Fragment] subclass.
|
||||||
*/
|
*/
|
||||||
class CategoryFragment : ListWithAddButtonFragment<CategoryViewModel>() {
|
class CategoryFragment : ListWithAddButtonFragment<CategoryViewModel, TransactionState>() {
|
||||||
override val noItemsStringRes: Int = R.string.transactions_no_data
|
override val noItemsStringRes: Int = R.string.transactions_no_data
|
||||||
override val viewModelClass: Class<CategoryViewModel> = CategoryViewModel::class.java
|
override val viewModelClass: Class<CategoryViewModel> = CategoryViewModel::class.java
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class CategoryFragment : ListWithAddButtonFragment<CategoryViewModel>() {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadItems(): Pair<List<BindableState>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<in BindableState>>> {
|
override suspend fun loadItems(): Pair<List<TransactionState>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<TransactionState>>> {
|
||||||
val categoryId = arguments?.getLong(EXTRA_CATEGORY_ID)
|
val categoryId = arguments?.getLong(EXTRA_CATEGORY_ID)
|
||||||
if (categoryId == null) {
|
if (categoryId == null) {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
|
@ -40,12 +40,12 @@ class CategoryFragment : ListWithAddButtonFragment<CategoryViewModel>() {
|
||||||
val category = viewModel.getCategory(categoryId)
|
val category = viewModel.getCategory(categoryId)
|
||||||
activity?.title = category.title
|
activity?.title = category.title
|
||||||
// TODO: Add category details here as well
|
// TODO: Add category details here as well
|
||||||
val items = ArrayList<BindableState>()
|
val items = ArrayList<TransactionState>()
|
||||||
items.addAll(viewModel.getTransactions(categoryId).map { TransactionState(it) })
|
items.addAll(viewModel.getTransactions(categoryId).map { TransactionState(it) })
|
||||||
return Pair(
|
return Pair(
|
||||||
items,
|
items,
|
||||||
mapOf(TRANSACTION_VIEW to { v ->
|
mapOf(TRANSACTION_VIEW to { v ->
|
||||||
TransactionViewHolder(v, findNavController()) as BindableAdapter.BindableViewHolder<in BindableState>
|
TransactionViewHolder(v, findNavController())
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import com.wbrawner.budget.ui.base.BindableState
|
||||||
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
|
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class CategoryListFragment : ListWithAddButtonFragment<CategoryViewModel>() {
|
class CategoryListFragment : ListWithAddButtonFragment<CategoryViewModel, CategoryState>() {
|
||||||
override val noItemsStringRes: Int = R.string.categories_no_data
|
override val noItemsStringRes: Int = R.string.categories_no_data
|
||||||
override val viewModelClass: Class<CategoryViewModel> = CategoryViewModel::class.java
|
override val viewModelClass: Class<CategoryViewModel> = CategoryViewModel::class.java
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class CategoryListFragment : ListWithAddButtonFragment<CategoryViewModel>() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadItems(): Pair<List<BindableState>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<in BindableState>>> {
|
override suspend fun loadItems(): Pair<List<CategoryState>, Map<Int, (view: View) -> CategoryViewHolder>> {
|
||||||
val budgetId = arguments?.getLong(EXTRA_BUDGET_ID)
|
val budgetId = arguments?.getLong(EXTRA_BUDGET_ID)
|
||||||
if (budgetId == null) {
|
if (budgetId == null) {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
|
@ -38,7 +38,7 @@ class CategoryListFragment : ListWithAddButtonFragment<CategoryViewModel>() {
|
||||||
return Pair(
|
return Pair(
|
||||||
viewModel.getCategories(budgetId).map { CategoryState(it) },
|
viewModel.getCategories(budgetId).map { CategoryState(it) },
|
||||||
mapOf(CATEGORY_VIEW to { v ->
|
mapOf(CATEGORY_VIEW to { v ->
|
||||||
CategoryViewHolder(v, viewModel, findNavController()) as BindableAdapter.BindableViewHolder<in BindableState>
|
CategoryViewHolder(v, viewModel, findNavController())
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class CategoryListViewModel @Inject constructor(
|
||||||
suspend fun getCategory(id: Long): Category = categoryRepo.findById(id)
|
suspend fun getCategory(id: Long): Category = categoryRepo.findById(id)
|
||||||
|
|
||||||
suspend fun getCategories(budgetId: Long? = null): Collection<Category> =
|
suspend fun getCategories(budgetId: Long? = null): Collection<Category> =
|
||||||
categoryRepo.findAll(budgetId)
|
categoryRepo.findAll(budgetId?.let { arrayOf(it) })
|
||||||
|
|
||||||
suspend fun saveCategory(category: Category) = if (category.id == null) categoryRepo.create(category)
|
suspend fun saveCategory(category: Category) = if (category.id == null) categoryRepo.create(category)
|
||||||
else categoryRepo.update(category)
|
else categoryRepo.update(category)
|
||||||
|
|
|
@ -35,7 +35,7 @@ class CategoryViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCategories(budgetId: Long? = null): Collection<Category> = showLoader {
|
suspend fun getCategories(budgetId: Long? = null): Collection<Category> = showLoader {
|
||||||
categoryRepo.findAll(budgetId)
|
categoryRepo.findAll(budgetId?.let { arrayOf(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveCategory(category: Category) = showLoader {
|
suspend fun saveCategory(category: Category) = showLoader {
|
||||||
|
|
|
@ -16,7 +16,7 @@ class AddEditTransactionViewModel @Inject constructor(
|
||||||
private val categoryRepository: CategoryRepository,
|
private val categoryRepository: CategoryRepository,
|
||||||
private val transactionRepository: TransactionRepository
|
private val transactionRepository: TransactionRepository
|
||||||
) : ViewModel() {
|
) : 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)
|
suspend fun getTransaction(id: Long) = transactionRepository.findById(id)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,14 @@ package com.wbrawner.budget.ui.transactions
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.findNavController
|
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.BindableAdapter
|
||||||
import com.wbrawner.budget.ui.base.BindableState
|
import com.wbrawner.budget.ui.base.BindableState
|
||||||
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
|
import com.wbrawner.budget.ui.base.ListWithAddButtonFragment
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class TransactionListFragment : ListWithAddButtonFragment<TransactionListViewModel>() {
|
class TransactionListFragment : ListWithAddButtonFragment<TransactionListViewModel, TransactionState>() {
|
||||||
override val viewModelClass: Class<TransactionListViewModel> = TransactionListViewModel::class.java
|
override val viewModelClass: Class<TransactionListViewModel> = TransactionListViewModel::class.java
|
||||||
override val noItemsStringRes: Int = R.string.transactions_no_data
|
override val noItemsStringRes: Int = R.string.transactions_no_data
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
(requireActivity().application as AllowanceApplication).appComponent.inject(this)
|
(requireActivity().application as AllowanceApplication).appComponent.inject(this)
|
||||||
super.onCreate(savedInstanceState)
|
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() {
|
override fun addItem() {
|
||||||
startActivity(Intent(activity, AddEditTransactionActivity::class.java))
|
startActivity(Intent(activity, AddEditTransactionActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadItems(): Pair<List<BindableState>, Map<Int, (view: View) -> BindableAdapter.BindableViewHolder<in BindableState>>> {
|
override suspend fun loadItems(): Pair<List<TransactionState>, Map<Int, (View) -> TransactionViewHolder>> = loadItems(
|
||||||
return Pair(
|
arguments?.getLong(EXTRA_BUDGET_ID),
|
||||||
viewModel.getTransactions(
|
arguments?.getLong(EXTRA_CATEGORY_ID)
|
||||||
arguments?.getLong(EXTRA_BUDGET_ID),
|
)
|
||||||
arguments?.getLong(EXTRA_CATEGORY_ID)
|
|
||||||
).map { TransactionState(it) },
|
private suspend fun loadItems(
|
||||||
mapOf(TRANSACTION_VIEW to { v ->
|
budgetId: Long?,
|
||||||
TransactionViewHolder(v, findNavController()) as BindableAdapter.BindableViewHolder<in BindableState>
|
categoryId: Long?,
|
||||||
})
|
from: Calendar? = null,
|
||||||
)
|
to: Calendar? = null
|
||||||
}
|
): Pair<List<TransactionState>, Map<Int, (View) -> TransactionViewHolder>> = Pair(
|
||||||
|
viewModel.getTransactions(budgetId, categoryId, from, to).map { TransactionState(it) },
|
||||||
|
mapOf(TRANSACTION_VIEW to { v ->
|
||||||
|
TransactionViewHolder(v, findNavController())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG_FRAGMENT = "transactions"
|
const val TAG_FRAGMENT = "transactions"
|
||||||
|
|
|
@ -7,12 +7,18 @@ import com.wbrawner.budget.ui.base.LoadingViewModel
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TransactionListViewModel @Inject constructor(private val transactionRepo: TransactionRepository) :
|
class TransactionListViewModel @Inject constructor(private val transactionRepo: TransactionRepository) :
|
||||||
LoadingViewModel() {
|
LoadingViewModel() {
|
||||||
suspend fun getTransactions(budgetId: Long? = null, categoryId: Long? = null) = showLoader {
|
suspend fun getTransactions(
|
||||||
transactionRepo.findAll(budgetId = budgetId, categoryId = categoryId)
|
budgetId: Long? = null,
|
||||||
|
categoryId: Long? = null,
|
||||||
|
start: Calendar? = null,
|
||||||
|
end: Calendar? = null
|
||||||
|
) = showLoader {
|
||||||
|
transactionRepo.findAll(budgetId, categoryId, start, end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
app/src/main/res/drawable/ic_filter_list.xml
Normal file
10
app/src/main/res/drawable/ic_filter_list.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
|
||||||
|
</vector>
|
|
@ -9,10 +9,10 @@
|
||||||
<path
|
<path
|
||||||
android:pathData="m74.798,72.259l0,14.924 0,6.993c0,23.605 18.905,42.982 42.329,43.75l0,0.027l7.463,0 1.445,0 1.971,0l0,3.874 0,14.456 0,11.467c0,9.885 4.738,19.192 12.731,25.008 7.993,5.816 18.305,7.46 27.711,4.419l-4.591,-14.201c-4.877,1.577 -10.194,0.728 -14.338,-2.288 -4.144,-3.015 -6.587,-7.813 -6.587,-12.939l0,-11.467l3.411,0 6.019,0 1.444,0l0,-0.024c23.425,-0.768 42.33,-20.145 42.33,-43.749l0,-7.463 0,-14.456l-7.463,0 -9.43,0 -1.445,0 -7.462,0l0,0.024c-11.392,0.374 -21.714,5.148 -29.313,12.67 -5.353,-17.472 -21.398,-30.374 -40.426,-30.999l0,-0.024l-1.444,0 -6.018,0 -10.874,0zM89.723,87.183l3.412,0 6.018,0c16.021,0 28.849,12.829 28.849,28.85l0,6.993l-1.967,0 -1.445,0 -6.018,0c-16.021,0 -28.849,-12.828 -28.849,-28.85zM171.781,105.513l6.017,0 1.445,0 1.968,0l0,6.994c0,16.021 -12.828,28.849 -28.849,28.849l-6.019,0 -3.411,0l0,-6.993c0,-16.021 12.827,-28.85 28.849,-28.85z"
|
android:pathData="m74.798,72.259l0,14.924 0,6.993c0,23.605 18.905,42.982 42.329,43.75l0,0.027l7.463,0 1.445,0 1.971,0l0,3.874 0,14.456 0,11.467c0,9.885 4.738,19.192 12.731,25.008 7.993,5.816 18.305,7.46 27.711,4.419l-4.591,-14.201c-4.877,1.577 -10.194,0.728 -14.338,-2.288 -4.144,-3.015 -6.587,-7.813 -6.587,-12.939l0,-11.467l3.411,0 6.019,0 1.444,0l0,-0.024c23.425,-0.768 42.33,-20.145 42.33,-43.749l0,-7.463 0,-14.456l-7.463,0 -9.43,0 -1.445,0 -7.462,0l0,0.024c-11.392,0.374 -21.714,5.148 -29.313,12.67 -5.353,-17.472 -21.398,-30.374 -40.426,-30.999l0,-0.024l-1.444,0 -6.018,0 -10.874,0zM89.723,87.183l3.412,0 6.018,0c16.021,0 28.849,12.829 28.849,28.85l0,6.993l-1.967,0 -1.445,0 -6.018,0c-16.021,0 -28.849,-12.828 -28.849,-28.85zM171.781,105.513l6.017,0 1.445,0 1.968,0l0,6.994c0,16.021 -12.828,28.849 -28.849,28.849l-6.019,0 -3.411,0l0,-6.993c0,-16.021 12.827,-28.85 28.849,-28.85z"
|
||||||
android:strokeWidth=".586787"
|
android:strokeWidth=".586787"
|
||||||
android:fillColor="@color/colorTextPrimary" />
|
android:fillColor="#1a1a1a" />
|
||||||
<path
|
<path
|
||||||
android:pathData="m89.723,87.183l0,6.993c0,16.022 12.828,28.85 28.849,28.85l6.018,0 1.445,0 1.967,0l0,-6.993c0,-16.021 -12.828,-28.85 -28.849,-28.85l-6.018,0z"
|
android:pathData="m89.723,87.183l0,6.993c0,16.022 12.828,28.85 28.849,28.85l6.018,0 1.445,0 1.967,0l0,-6.993c0,-16.021 -12.828,-28.85 -28.849,-28.85l-6.018,0z"
|
||||||
android:strokeWidth=".586787"
|
android:strokeWidth=".586787"
|
||||||
android:fillColor="@color/colorAccent" />
|
android:fillColor="#bfff00" />
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
18
app/src/main/res/drawable/ic_twigs_color.xml
Normal file
18
app/src/main/res/drawable/ic_twigs_color.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="416.8205"
|
||||||
|
android:viewportHeight="416.82053">
|
||||||
|
<group
|
||||||
|
android:translateX="72.94359"
|
||||||
|
android:translateY="72.943596">
|
||||||
|
<path
|
||||||
|
android:pathData="m74.798,72.259l0,14.924 0,6.993c0,23.605 18.905,42.982 42.329,43.75l0,0.027l7.463,0 1.445,0 1.971,0l0,3.874 0,14.456 0,11.467c0,9.885 4.738,19.192 12.731,25.008 7.993,5.816 18.305,7.46 27.711,4.419l-4.591,-14.201c-4.877,1.577 -10.194,0.728 -14.338,-2.288 -4.144,-3.015 -6.587,-7.813 -6.587,-12.939l0,-11.467l3.411,0 6.019,0 1.444,0l0,-0.024c23.425,-0.768 42.33,-20.145 42.33,-43.749l0,-7.463 0,-14.456l-7.463,0 -9.43,0 -1.445,0 -7.462,0l0,0.024c-11.392,0.374 -21.714,5.148 -29.313,12.67 -5.353,-17.472 -21.398,-30.374 -40.426,-30.999l0,-0.024l-1.444,0 -6.018,0 -10.874,0zM89.723,87.183l3.412,0 6.018,0c16.021,0 28.849,12.829 28.849,28.85l0,6.993l-1.967,0 -1.445,0 -6.018,0c-16.021,0 -28.849,-12.828 -28.849,-28.85zM171.781,105.513l6.017,0 1.445,0 1.968,0l0,6.994c0,16.021 -12.828,28.849 -28.849,28.849l-6.019,0 -3.411,0l0,-6.993c0,-16.021 12.827,-28.85 28.849,-28.85z"
|
||||||
|
android:strokeWidth=".586787"
|
||||||
|
android:fillColor="@color/colorTextPrimary" />
|
||||||
|
<path
|
||||||
|
android:pathData="m89.723,87.183l0,6.993c0,16.022 12.828,28.85 28.849,28.85l6.018,0 1.445,0 1.967,0l0,-6.993c0,-16.021 -12.828,-28.85 -28.849,-28.85l-6.018,0z"
|
||||||
|
android:strokeWidth=".586787"
|
||||||
|
android:fillColor="@color/colorAccent" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -22,18 +22,16 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/action_bar">
|
app:layout_constraintTop_toBottomOf="@+id/action_bar">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/container_edit_category_name"
|
android:id="@+id/container_edit_category_name"
|
||||||
style="@style/AppTheme.EditText.Container"
|
style="@style/AppTheme.EditText.Container"
|
||||||
android:hint="@string/prompt_category_name"
|
android:hint="@string/prompt_category_name">
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/edit_category_name"
|
android:id="@+id/edit_category_name"
|
||||||
|
@ -44,10 +42,7 @@
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/container_edit_category_amount"
|
android:id="@+id/container_edit_category_amount"
|
||||||
style="@style/AppTheme.EditText.Container"
|
style="@style/AppTheme.EditText.Container"
|
||||||
android:hint="@string/prompt_category_amount"
|
android:hint="@string/prompt_category_amount">
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/container_edit_category_name">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/edit_category_amount"
|
android:id="@+id/edit_category_amount"
|
||||||
|
@ -60,15 +55,12 @@
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/prompt_account"
|
android:text="@string/prompt_budget" />
|
||||||
app:layout_constraintTop_toBottomOf="@+id/container_edit_category_amount"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSpinner
|
<androidx.appcompat.widget.AppCompatSpinner
|
||||||
android:id="@+id/budgetSpinner"
|
android:id="@+id/budgetSpinner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
app:layout_constraintTop_toBottomOf="@+id/budgetHint" />
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
|
app:backgroundTint="@color/colorPrimary"
|
||||||
app:srcCompat="@drawable/ic_add_white_24dp" />
|
app:srcCompat="@drawable/ic_add_white_24dp" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
9
app/src/main/res/menu/menu_transaction_list.xml
Normal file
9
app/src/main/res/menu/menu_transaction_list.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/filter"
|
||||||
|
android:icon="@drawable/ic_filter_list"
|
||||||
|
android:title="@string/action_filter"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
|
@ -3,8 +3,8 @@
|
||||||
<color name="colorBackgroundPrimary">#FFFFFFFF</color>
|
<color name="colorBackgroundPrimary">#FFFFFFFF</color>
|
||||||
<color name="colorPrimary">#30d158</color>
|
<color name="colorPrimary">#30d158</color>
|
||||||
<color name="colorPrimaryDark">#34c759</color>
|
<color name="colorPrimaryDark">#34c759</color>
|
||||||
<color name="colorAccent">#bfff00</color>
|
<color name="colorAccent">#30d158</color>
|
||||||
<color name="colorTextPrimary">#FF000000</color>
|
<color name="colorTextPrimary">#1a1a1a</color>
|
||||||
<color name="colorTextGreen">#388e3c</color>
|
<color name="colorTextGreen">#388e3c</color>
|
||||||
<color name="colorTextRed">#d32f2f</color>
|
<color name="colorTextRed">#d32f2f</color>
|
||||||
<color name="colorTextPrimaryInverted">#FFDADADA</color>
|
<color name="colorTextPrimaryInverted">#FFDADADA</color>
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
<string name="action_login">Login</string>
|
<string name="action_login">Login</string>
|
||||||
<string name="title_register">Register</string>
|
<string name="title_register">Register</string>
|
||||||
<string name="title_forgot_password">Forgot password?</string>
|
<string name="title_forgot_password">Forgot password?</string>
|
||||||
<string name="prompt_account">Account</string>
|
|
||||||
<string name="prompt_account_name">Name</string>
|
<string name="prompt_account_name">Name</string>
|
||||||
<string name="prompt_account_description">Description</string>
|
<string name="prompt_account_description">Description</string>
|
||||||
<string name="prompt_share_with">Share with...</string>
|
<string name="prompt_share_with">Share with...</string>
|
||||||
|
@ -50,4 +49,8 @@
|
||||||
<string name="title_edit_budget">Edit Budget</string>
|
<string name="title_edit_budget">Edit Budget</string>
|
||||||
<string name="title_edit">Edit</string>
|
<string name="title_edit">Edit</string>
|
||||||
<string name="description_logo">Twigs Logo</string>
|
<string name="description_logo">Twigs Logo</string>
|
||||||
|
<string name="action_filter">Filter</string>
|
||||||
|
<string name="action_cancel">Cancel</string>
|
||||||
|
<string name="prompt_month">Month</string>
|
||||||
|
<string name="prompt_year">Year</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -29,7 +29,8 @@ dependencies {
|
||||||
// Retrofit
|
// Retrofit
|
||||||
api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit"
|
api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit"
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:$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"
|
api "com.squareup.moshi:moshi:$rootProject.ext.moshi"
|
||||||
implementation "com.squareup.moshi:moshi-adapters:$rootProject.ext.moshi"
|
implementation "com.squareup.moshi:moshi-adapters:$rootProject.ext.moshi"
|
||||||
// Dagger
|
// Dagger
|
||||||
|
|
|
@ -37,7 +37,7 @@ interface BudgetApiService {
|
||||||
// Categories
|
// Categories
|
||||||
@GET("categories")
|
@GET("categories")
|
||||||
suspend fun getCategories(
|
suspend fun getCategories(
|
||||||
@Query("budgetId") budgetId: Long? = null,
|
@Query("budgetIds") budgetIds: Array<Long>? = null,
|
||||||
@Query("count") count: Int? = null,
|
@Query("count") count: Int? = null,
|
||||||
@Query("page") page: Int? = null
|
@Query("page") page: Int? = null
|
||||||
): Collection<Category>
|
): Collection<Category>
|
||||||
|
@ -65,6 +65,8 @@ interface BudgetApiService {
|
||||||
suspend fun getTransactions(
|
suspend fun getTransactions(
|
||||||
@Query("budgetId") budgetId: Long? = null,
|
@Query("budgetId") budgetId: Long? = null,
|
||||||
@Query("categoryId") categoryId: Long? = null,
|
@Query("categoryId") categoryId: Long? = null,
|
||||||
|
@Query("from") from: String? = null,
|
||||||
|
@Query("to") to: String? = null,
|
||||||
@Query("count") count: Int? = null,
|
@Query("count") count: Int? = null,
|
||||||
@Query("page") page: Int? = null
|
@Query("page") page: Int? = null
|
||||||
): Collection<Transaction>
|
): Collection<Transaction>
|
||||||
|
|
|
@ -12,12 +12,14 @@ import com.wbrawner.budget.lib.repository.NetworkBudgetRepository
|
||||||
import com.wbrawner.budget.lib.repository.NetworkCategoryRepository
|
import com.wbrawner.budget.lib.repository.NetworkCategoryRepository
|
||||||
import com.wbrawner.budget.lib.repository.NetworkTransactionRepository
|
import com.wbrawner.budget.lib.repository.NetworkTransactionRepository
|
||||||
import com.wbrawner.budget.lib.repository.NetworkUserRepository
|
import com.wbrawner.budget.lib.repository.NetworkUserRepository
|
||||||
|
import com.wbrawner.budgetlib.BuildConfig
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.CookieJar
|
import okhttp3.CookieJar
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
@ -34,10 +36,10 @@ class NetworkModule {
|
||||||
@Provides
|
@Provides
|
||||||
fun provideCookieJar(sharedPreferences: SharedPreferences): CookieJar {
|
fun provideCookieJar(sharedPreferences: SharedPreferences): CookieJar {
|
||||||
return object : CookieJar {
|
return object : CookieJar {
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
sharedPreferences.edit()
|
sharedPreferences.edit()
|
||||||
.putString(
|
.putString(
|
||||||
url.host(),
|
url.host,
|
||||||
cookies.joinToString(separator = ",") {
|
cookies.joinToString(separator = ",") {
|
||||||
Base64.encode(it.toString().toByteArray(), 0)
|
Base64.encode(it.toString().toByteArray(), 0)
|
||||||
.toString(charset = Charset.forName("UTF-8"))
|
.toString(charset = Charset.forName("UTF-8"))
|
||||||
|
@ -47,7 +49,7 @@ class NetworkModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
|
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
|
||||||
return sharedPreferences.getString(url.host(), "")
|
return sharedPreferences.getString(url.host, "")
|
||||||
?.split(",")
|
?.split(",")
|
||||||
?.mapNotNull {
|
?.mapNotNull {
|
||||||
Cookie.parse(
|
Cookie.parse(
|
||||||
|
@ -64,7 +66,13 @@ class NetworkModule {
|
||||||
@Provides
|
@Provides
|
||||||
fun provideOkHttpClient(cookieJar: CookieJar): OkHttpClient = OkHttpClient.Builder()
|
fun provideOkHttpClient(cookieJar: CookieJar): OkHttpClient = OkHttpClient.Builder()
|
||||||
.cookieJar(cookieJar)
|
.cookieJar(cookieJar)
|
||||||
// TODO: Add Gander interceptor
|
.apply {
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
this.addInterceptor(
|
||||||
|
HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT)
|
||||||
|
.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||||
|
)
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -7,7 +7,7 @@ import javax.inject.Inject
|
||||||
class NetworkCategoryRepository @Inject constructor(private val apiService: BudgetApiService) : CategoryRepository {
|
class NetworkCategoryRepository @Inject constructor(private val apiService: BudgetApiService) : CategoryRepository {
|
||||||
override suspend fun create(newItem: Category): Category = apiService.newCategory(newItem)
|
override suspend fun create(newItem: Category): Category = apiService.newCategory(newItem)
|
||||||
|
|
||||||
override suspend fun findAll(accountId: Long?): Collection<Category> = apiService.getCategories(accountId).sortedBy { it.title }
|
override suspend fun findAll(budgetIds: Array<Long>?): Collection<Category> = apiService.getCategories(budgetIds).sortedBy { it.title }
|
||||||
|
|
||||||
override suspend fun findAll(): Collection<Category> = findAll(null)
|
override suspend fun findAll(): Collection<Category> = findAll(null)
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,33 @@ package com.wbrawner.budget.lib.repository
|
||||||
import com.wbrawner.budget.common.transaction.Transaction
|
import com.wbrawner.budget.common.transaction.Transaction
|
||||||
import com.wbrawner.budget.common.transaction.TransactionRepository
|
import com.wbrawner.budget.common.transaction.TransactionRepository
|
||||||
import com.wbrawner.budget.lib.network.BudgetApiService
|
import com.wbrawner.budget.lib.network.BudgetApiService
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NetworkTransactionRepository @Inject constructor(private val apiService: BudgetApiService) : TransactionRepository {
|
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 create(newItem: Transaction): Transaction = apiService.newTransaction(newItem)
|
||||||
|
|
||||||
override suspend fun findAll(budgetId: Long?, categoryId: Long?): Collection<Transaction> =
|
override suspend fun findAll(
|
||||||
apiService.getTransactions(budgetId, categoryId).sortedByDescending { it.date }
|
budgetId: Long?,
|
||||||
|
categoryId: Long?,
|
||||||
|
start: Calendar?,
|
||||||
|
end: Calendar?
|
||||||
|
): Collection<Transaction> = 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<Transaction> = findAll(null)
|
override suspend fun findAll(): Collection<Transaction> = findAll(null)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,6 @@ package com.wbrawner.budget.common.category
|
||||||
import com.wbrawner.budget.common.Repository
|
import com.wbrawner.budget.common.Repository
|
||||||
|
|
||||||
interface CategoryRepository : Repository<Category, Long> {
|
interface CategoryRepository : Repository<Category, Long> {
|
||||||
suspend fun findAll(accountId: Long? = null): Collection<Category>
|
suspend fun findAll(budgetIds: Array<Long>? = null): Collection<Category>
|
||||||
suspend fun getBalance(id: Long): Long
|
suspend fun getBalance(id: Long): Long
|
||||||
}
|
}
|
|
@ -1,7 +1,13 @@
|
||||||
package com.wbrawner.budget.common.transaction
|
package com.wbrawner.budget.common.transaction
|
||||||
|
|
||||||
import com.wbrawner.budget.common.Repository
|
import com.wbrawner.budget.common.Repository
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
interface TransactionRepository : Repository<Transaction, Long> {
|
interface TransactionRepository : Repository<Transaction, Long> {
|
||||||
suspend fun findAll(budgetId: Long? = null, categoryId: Long? = null): Collection<Transaction>
|
suspend fun findAll(
|
||||||
|
budgetId: Long? = null,
|
||||||
|
categoryId: Long? = null,
|
||||||
|
start: Calendar? = null,
|
||||||
|
end: Calendar? = null
|
||||||
|
): Collection<Transaction>
|
||||||
}
|
}
|
Loading…
Reference in a new issue