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