From 7b1c0db0cc9d1b8ca7605e36ca4a1033dc2de2d0 Mon Sep 17 00:00:00 2001 From: Hyperling Date: Thu, 9 Jan 2025 15:00:35 -0700 Subject: [PATCH] Add view model and modify MainActivity. Should be everything needed to start testing near-basic functionality and begin tweaking as needed! --- .../expensetracker/ExpenseViewModel.kt | 98 ++++++++++++ .../expensetracker/MainActivity-OLD.kt | 140 ++++++++++++++++++ .../hyperling/expensetracker/MainActivity.kt | 140 +++++------------- 3 files changed, 271 insertions(+), 107 deletions(-) create mode 100644 app/src/main/java/com/hyperling/expensetracker/ExpenseViewModel.kt create mode 100644 app/src/main/java/com/hyperling/expensetracker/MainActivity-OLD.kt diff --git a/app/src/main/java/com/hyperling/expensetracker/ExpenseViewModel.kt b/app/src/main/java/com/hyperling/expensetracker/ExpenseViewModel.kt new file mode 100644 index 0000000..a01554c --- /dev/null +++ b/app/src/main/java/com/hyperling/expensetracker/ExpenseViewModel.kt @@ -0,0 +1,98 @@ +package com.hyperling.expensetracker + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class ExpenseViewModel ( + private val dao: ExpenseDao, +): ViewModel() { + + private val _sortType = MutableStateFlow(SortType.NAME) + private val _state = MutableStateFlow(ExpenseState()) + + private val _expenses = _sortType + .flatMapLatest { sortType -> + when(sortType) { + SortType.NAME -> dao.selectExpensesName() + SortType.COST -> dao.selectExpensesCost() + SortType.RATE -> dao.selectExpensesRate() + } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) + + val state = combine(_state, _sortType, _expenses) { state, sortType, expenses -> + state.copy( + expenses = expenses, + sortType = sortType + ) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ExpenseState()) + + fun onEvent(event: ExpenseEvent) { + when(event) { + is ExpenseEvent.DeleteExpense -> { + viewModelScope.launch { + dao.deleteExpense(event.expense) + } + } + ExpenseEvent.HideDialog -> { + _state.update { it.copy( + isAddingExpense = false + ) } + } + ExpenseEvent.SaveExpense -> { + val name = state.value.name + val cost = state.value.cost + val rate = state.value.rate + + if (name.isBlank() + || cost.isBlank() + //|| rate.isBlank() # TBD, enable this once Rate is working. + ) { + return + } + + val expense = Expense(name, cost, rate) + + viewModelScope.launch { + dao.upsertExpense(expense) + } + + _state.update { it.copy( + isAddingExpense = false, + name = "", + cost = "", + rate = Rate.MONTHLY, + ) } + } + is ExpenseEvent.SetName -> { + _state.update { it.copy( + name = event.name + ) } + } + is ExpenseEvent.SetCost -> { + _state.update { it.copy( + cost = event.cost + ) } + } + is ExpenseEvent.SetRate -> { + _state.update { it.copy( + rate = event.rate + ) } + } + ExpenseEvent.ShowDialog -> { + _state.update { it.copy( + isAddingExpense = true + ) } + } + is ExpenseEvent.SortExpenses -> { + _sortType.value = event.sortType + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hyperling/expensetracker/MainActivity-OLD.kt b/app/src/main/java/com/hyperling/expensetracker/MainActivity-OLD.kt new file mode 100644 index 0000000..5e605e1 --- /dev/null +++ b/app/src/main/java/com/hyperling/expensetracker/MainActivity-OLD.kt @@ -0,0 +1,140 @@ +package com.hyperling.expensetracker + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.startActivity +import com.hyperling.expensetracker.ui.theme.ExpenseTrackerTheme + +class MainActivityOLD : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + ExpenseTrackerTheme { + Surface { + Main() + } + } + } + } +} + +// This seems like a great tutorial for what I need to do! +// https://www.answertopia.com/jetpack-compose/a-jetpack-compose-room-database-and-repository-tutorial/ + +@Composable +fun Main() { + val context = LocalContext.current + + var sumDaily by remember { mutableStateOf(0.0) } + var sumWeekly by remember { mutableStateOf(0.0) } + var sumMonthly by remember { mutableStateOf(0.0) } + var sumYearly by remember { mutableStateOf(0.0) } + + val sums = mapOf( + "Daily" to sumDaily, + "Weekly" to sumWeekly, + "Monthly" to sumMonthly, + "Yearly" to sumYearly, + ) + + Column ( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize() + ) { + + Text( + text = "Current Expense Summary" + ) + + //for ((name, value) in sums) { + sums.forEach { (name, value) -> + Row ( + horizontalArrangement = Arrangement.End, + ){ + Text(text = name + ": ") + Text(text = value.toString()) + } + } + + // FORTESTING + Text (text = String.format("%.2f",sumDaily)) + Text (text = String.format("%.2f",sumWeekly)) + Text (text = String.format("%.2f",sumMonthly)) + Text (text = String.format("%.2f",sumYearly)) + + Row ( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Button(onClick = { + val intent = Intent(context, NewExpenseActivity::class.java) + context.startActivity(intent) + }) { + Text(text = "Create New Expense") + } + } + + Text(text = "Expenses") + // https://medium.com/@rowaido.game/implementing-the-room-library-with-jetpack-compose-590d13101fa7 + /* TODO: + ForEach over all DB records, show it, and add its value to the summary. + */ + val expenseArray = listOf( + Expense("Test", 180.0, 'Y', "My notes."), + Expense("Test2", 20.0, 'M', "My notes, 2!"), + ) + expenseArray.forEach { expense -> + when (expense.freq) { + 'D' -> sumYearly += (expense.cost * 365.25) + 'W' -> sumYearly += (expense.cost * (365.25 / 7)) + 'M' -> sumYearly += (expense.cost * 12) + 'Y' -> sumYearly += (expense.cost) + } + + sumDaily = sumYearly / 365.25 + sumWeekly = sumYearly / (365.25 / 7) + sumMonthly = sumYearly / 12 + } + + // FORTESTING + Text (text = String.format("$ %.2f",sumDaily)) + Text (text = String.format("$ %.2f",sumWeekly)) + Text (text = String.format("$ %.2f",sumMonthly)) + Text (text = String.format("$ %.2f",sumYearly)) + } +} + +@Preview(showBackground = true) +@Composable +fun MainPreview() { + ExpenseTrackerTheme { + Main() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hyperling/expensetracker/MainActivity.kt b/app/src/main/java/com/hyperling/expensetracker/MainActivity.kt index 5cce765..1bd79a8 100644 --- a/app/src/main/java/com/hyperling/expensetracker/MainActivity.kt +++ b/app/src/main/java/com/hyperling/expensetracker/MainActivity.kt @@ -1,140 +1,66 @@ package com.hyperling.expensetracker -import android.content.Context -import android.content.Intent +import com.hyperling.expensetracker.ui.theme.ExpenseTrackerTheme import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.activity.viewModels import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import androidx.core.content.ContextCompat -import androidx.core.content.ContextCompat.startActivity -import com.hyperling.expensetracker.ui.theme.ExpenseTrackerTheme +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.room.Room +import androidx.compose.material3.Surface class MainActivity : ComponentActivity() { + + private val _db by lazy { + Room.databaseBuilder( + applicationContext, + ExpenseDatabase::class.java, + "expenses.db" + ).build() + } + private val _viewModel by viewModels ( + factoryProducer = { + object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return ExpenseViewModel(_db.dao) as T + } + } + } + ) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { ExpenseTrackerTheme { - Surface { - Main() - } + val state by _viewModel.state.collectAsState() + ExpenseScreen( + state = state, + onEvent = _viewModel::onEvent + ) } } } } -// This seems like a great tutorial for what I need to do! -// https://www.answertopia.com/jetpack-compose/a-jetpack-compose-room-database-and-repository-tutorial/ - -@Composable -fun Main() { - val context = LocalContext.current - - var sumDaily by remember { mutableStateOf(0.0) } - var sumWeekly by remember { mutableStateOf(0.0) } - var sumMonthly by remember { mutableStateOf(0.0) } - var sumYearly by remember { mutableStateOf(0.0) } - - val sums = mapOf( - "Daily" to sumDaily, - "Weekly" to sumWeekly, - "Monthly" to sumMonthly, - "Yearly" to sumYearly, - ) - - Column ( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxSize() - ) { - - Text( - text = "Current Expense Summary" - ) - - //for ((name, value) in sums) { - sums.forEach { (name, value) -> - Row ( - horizontalArrangement = Arrangement.End, - ){ - Text(text = name + ": ") - Text(text = value.toString()) - } - } - - // FORTESTING - Text (text = String.format("%.2f",sumDaily)) - Text (text = String.format("%.2f",sumWeekly)) - Text (text = String.format("%.2f",sumMonthly)) - Text (text = String.format("%.2f",sumYearly)) - - Row ( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Button(onClick = { - val intent = Intent(context, NewExpenseActivity::class.java) - context.startActivity(intent) - }) { - Text(text = "Create New Expense") - } - } - - Text(text = "Expenses") - // https://medium.com/@rowaido.game/implementing-the-room-library-with-jetpack-compose-590d13101fa7 - /* TODO: - ForEach over all DB records, show it, and add its value to the summary. - */ - val expenseArray = listOf( - Expense("Test", 180.0, 'Y', "My notes."), - Expense("Test2", 20.0, 'M', "My notes, 2!"), - ) - expenseArray.forEach { expense -> - when (expense.freq) { - 'D' -> sumYearly += (expense.cost * 365.25) - 'W' -> sumYearly += (expense.cost * (365.25 / 7)) - 'M' -> sumYearly += (expense.cost * 12) - 'Y' -> sumYearly += (expense.cost) - } - - sumDaily = sumYearly / 365.25 - sumWeekly = sumYearly / (365.25 / 7) - sumMonthly = sumYearly / 12 - } - - // FORTESTING - Text (text = String.format("$ %.2f",sumDaily)) - Text (text = String.format("$ %.2f",sumWeekly)) - Text (text = String.format("$ %.2f",sumMonthly)) - Text (text = String.format("$ %.2f",sumYearly)) - } -} - +/* @Preview(showBackground = true) @Composable fun MainPreview() { ExpenseTrackerTheme { Main() } -} \ No newline at end of file +} +*/ \ No newline at end of file