Add NavigationDrawer to MainActivity to switch between budgets or create new ones

This commit is contained in:
William Brawner 2020-12-29 18:06:39 -07:00
parent ace85f324f
commit ed734651f9
13 changed files with 233 additions and 41 deletions

View file

@ -4,6 +4,7 @@ import android.content.Context
import com.wbrawner.budget.common.util.ErrorHandler import com.wbrawner.budget.common.util.ErrorHandler
import com.wbrawner.budget.lib.network.NetworkModule import com.wbrawner.budget.lib.network.NetworkModule
import com.wbrawner.budget.storage.StorageModule import com.wbrawner.budget.storage.StorageModule
import com.wbrawner.budget.ui.MainViewModel
import com.wbrawner.budget.ui.SplashViewModel import com.wbrawner.budget.ui.SplashViewModel
import com.wbrawner.budget.ui.budgets.BudgetFormViewModel import com.wbrawner.budget.ui.budgets.BudgetFormViewModel
import com.wbrawner.budget.ui.budgets.BudgetListViewModel import com.wbrawner.budget.ui.budgets.BudgetListViewModel
@ -15,7 +16,6 @@ import com.wbrawner.budget.ui.transactions.TransactionFormViewModel
import com.wbrawner.budget.ui.transactions.TransactionListViewModel import com.wbrawner.budget.ui.transactions.TransactionListViewModel
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Component import dagger.Component
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@ -23,6 +23,7 @@ import javax.inject.Singleton
interface AppComponent { interface AppComponent {
fun inject(viewModel: OverviewViewModel) fun inject(viewModel: OverviewViewModel)
fun inject(viewModel: SplashViewModel) fun inject(viewModel: SplashViewModel)
fun inject(viewModel: MainViewModel)
fun inject(viewMode: BudgetListViewModel) fun inject(viewMode: BudgetListViewModel)
fun inject(viewModel: BudgetFormViewModel) fun inject(viewModel: BudgetFormViewModel)
fun inject(viewModel: CategoryListViewModel) fun inject(viewModel: CategoryListViewModel)

View file

@ -2,43 +2,95 @@ package com.wbrawner.budget.ui
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.google.android.material.navigation.NavigationView
import com.wbrawner.budget.AllowanceApplication
import com.wbrawner.budget.R import com.wbrawner.budget.R
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() { private const val MENU_GROUP_BUDGETS = 50
private const val MENU_ITEM_ADD_BUDGET = 100
private const val MENU_ITEM_SETTINGS = 101
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var toggle: ActionBarDrawerToggle
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
EmojiCompat.init(androidx.emoji.bundled.BundledEmojiCompatConfig(this)) EmojiCompat.init(androidx.emoji.bundled.BundledEmojiCompatConfig(this))
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
(application as AllowanceApplication).appComponent.inject(viewModel)
setSupportActionBar(action_bar) setSupportActionBar(action_bar)
toggle = ActionBarDrawerToggle(this, drawerLayout, R.string.action_open, R.string.action_close)
toggle.isDrawerIndicatorEnabled = true
toggle.isDrawerSlideAnimationEnabled = true
drawerLayout.addDrawerListener(toggle)
navigationView.setNavigationItemSelectedListener(this)
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val navController = findNavController(R.id.content_container) val navController = findNavController(R.id.content_container)
menu_main.setupWithNavController(navController) menu_main.setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ -> navController.addOnDestinationChangedListener { _, destination, _ ->
title = destination.label title = destination.label
val showHomeAsUp = when (destination.label) { val homeAsUpIndicator = when (destination.label) {
getString(R.string.title_overview) -> false getString(R.string.title_overview) -> R.drawable.ic_menu
getString(R.string.title_transactions) -> false getString(R.string.title_transactions) -> R.drawable.ic_menu
getString(R.string.title_profile) -> false getString(R.string.title_profile) -> R.drawable.ic_menu
getString(R.string.title_categories) -> false getString(R.string.title_categories) -> R.drawable.ic_menu
else -> true else -> 0
} }
supportActionBar?.setDisplayHomeAsUpEnabled(showHomeAsUp) supportActionBar?.setHomeAsUpIndicator(homeAsUpIndicator)
} }
viewModel.loadBudgets().observe(this, { list ->
val menu = navigationView.menu
menu.clear()
val budgetsMenu = navigationView.menu.addSubMenu(0, 0, 0, "Budgets")
list.budgets.forEachIndexed { index, budget ->
budgetsMenu.add(MENU_GROUP_BUDGETS, index, index, budget.name)
.setIcon(R.drawable.ic_folder_selectable)
}
budgetsMenu.setGroupCheckable(MENU_GROUP_BUDGETS, true, true)
list.selectedIndex?.let {
budgetsMenu.getItem(it).isChecked = true
}
menu.add(0, MENU_ITEM_ADD_BUDGET, list.budgets.size, R.string.title_add_budget)
.setIcon(R.drawable.ic_add_white_24dp)
menu.add(1, MENU_ITEM_SETTINGS, list.budgets.size + 1, "Settings")
.setIcon(R.drawable.ic_settings)
})
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId != android.R.id.home) return super.onOptionsItemSelected(item)
findNavController(R.id.content_container).navigateUp() with(findNavController(R.id.content_container)) {
return true when (currentDestination?.label) {
getString(R.string.title_overview) -> drawerLayout.open()
getString(R.string.title_transactions) -> drawerLayout.open()
getString(R.string.title_profile) -> drawerLayout.open()
getString(R.string.title_categories) -> drawerLayout.open()
else -> navigateUp()
}
} }
return super.onOptionsItemSelected(item) return true
} }
companion object { companion object {
const val EXTRA_OPEN_FRAGMENT = "com.wbrawner.budget.MainActivity.EXTRA_OPEN_FRAGMENT" const val EXTRA_OPEN_FRAGMENT = "com.wbrawner.budget.MainActivity.EXTRA_OPEN_FRAGMENT"
} }
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
MENU_ITEM_ADD_BUDGET -> findNavController(R.id.content_container).navigate(R.id.addEditBudget)
MENU_ITEM_SETTINGS -> findNavController(R.id.content_container).navigate(R.id.addEditBudget)
else -> viewModel.loadBudget(item.itemId)
}
drawerLayout.close()
return true
}
} }

View file

@ -0,0 +1,47 @@
package com.wbrawner.budget.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wbrawner.budget.common.budget.Budget
import com.wbrawner.budget.common.budget.BudgetRepository
import com.wbrawner.budget.common.user.UserRepository
import kotlinx.coroutines.launch
import javax.inject.Inject
class MainViewModel : ViewModel() {
@Inject
lateinit var budgetRepository: BudgetRepository
@Inject
lateinit var userRepository: UserRepository
private val budgets = MutableLiveData<BudgetList>()
fun loadBudgets(): LiveData<BudgetList> {
viewModelScope.launch {
val list = budgetRepository.findAll().sortedBy { it.name }
budgets.postValue(BudgetList(
list,
budgetRepository.currentBudget.value?.let {
list.indexOf(it)
}
))
}
return budgets
}
fun loadBudget(index: Int) {
val list = budgets.value ?: return
viewModelScope.launch {
budgetRepository.findById(list.budgets[index].id!!, true)
budgets.postValue(list.copy(selectedIndex = index))
}
}
}
data class BudgetList(
val budgets: List<Budget>,
val selectedIndex: Int? = null
)

View 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,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" />
</vector>

View 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="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z" />
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_folder" />
<item android:drawable="@drawable/ic_folder_open" />
</selector>

View 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="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
</vector>

View 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="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
</vector>

View file

@ -1,39 +1,56 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity"> tools:context=".ui.MainActivity">
<androidx.appcompat.widget.Toolbar <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/action_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="match_parent">
android:elevation="4dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView <androidx.appcompat.widget.Toolbar
android:id="@+id/menu_main" android:id="@+id/action_bar"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="?attr/actionBarSize"
app:layout_constraintBottom_toBottomOf="parent" android:elevation="4dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/main_navigation" /> app:layout_constraintTop_toTopOf="parent" />
<fragment <com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/content_container" android:id="@+id/menu_main"
android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp"
android:layout_width="match_parent" android:layout_height="56dp"
android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="@+id/menu_main" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:menu="@menu/bottom_navigation" />
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout> <fragment
android:id="@+id/content_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/menu_main"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/action_bar"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:paddingTop="16dp"
app:headerLayout="@layout/header_navigation_menu"
app:menu="@menu/drawer_navigation" />
</androidx.drawerlayout.widget.DrawerLayout>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/twigsIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_twigs_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/app_name"
style="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@+id/twigsIcon"
app:layout_constraintTop_toTopOf="@+id/twigsIcon"
app:layout_constraintStart_toEndOf="@+id/twigsIcon" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<menu />

View file

@ -59,4 +59,6 @@
<string name="label_balance">Balance</string> <string name="label_balance">Balance</string>
<string name="label_remaining">Remaining</string> <string name="label_remaining">Remaining</string>
<string name="prompt_server">Server</string> <string name="prompt_server">Server</string>
<string name="action_open">Open</string>
<string name="action_close">Close</string>
</resources> </resources>