Compare commits
8 Commits
49eb36514d
...
7b1c0db0cc
Author | SHA1 | Date | |
---|---|---|---|
7b1c0db0cc | |||
e3a2625f68 | |||
a4513394d1 | |||
d5d525f65a | |||
bf61d878cc | |||
6c95d33526 | |||
d8ecd7b621 | |||
845715122f |
@ -1,23 +1,13 @@
|
|||||||
package com.hyperling.expensetracker
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
/*class Expense {
|
import androidx.room.Entity
|
||||||
var name: String = ""
|
import androidx.room.PrimaryKey
|
||||||
var cost: Double = 0.0
|
|
||||||
var freq: Char = '*'
|
|
||||||
var note: String = ""
|
|
||||||
|
|
||||||
/*public Expense (val name: String, val cost: Double, val freq: Char, val note: String) {
|
@Entity
|
||||||
this.name = name
|
data class Expense (
|
||||||
this.name = name
|
val name: String,
|
||||||
this.name = name
|
val cost: String,
|
||||||
this.name = name
|
val rate: Enum<Rate>,
|
||||||
}*/
|
@PrimaryKey(autoGenerate = true)
|
||||||
}*/
|
val ID: Int = 0,
|
||||||
|
)
|
||||||
class Expense (
|
|
||||||
var name: String = "",
|
|
||||||
var cost: Double = 0.0,
|
|
||||||
var freq: Char = '*',
|
|
||||||
var note: String = "",
|
|
||||||
) {
|
|
||||||
}
|
|
30
app/src/main/java/com/hyperling/expensetracker/ExpenseDao.kt
Normal file
30
app/src/main/java/com/hyperling/expensetracker/ExpenseDao.kt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ExpenseDao {
|
||||||
|
|
||||||
|
// 2024-12-28
|
||||||
|
// If these complain about return type errors, try upgrading
|
||||||
|
// the ROOM version, otherwise remove the SUSPEND keyword.
|
||||||
|
@Upsert
|
||||||
|
suspend fun upsertExpense(expense: Expense)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteExpense(expense: Expense)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM expense ORDER BY name ASC")
|
||||||
|
fun selectExpensesName(): Flow<List<Expense>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM expense ORDER BY rate ASC")
|
||||||
|
fun selectExpensesRate(): Flow<List<Expense>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM expense ORDER BY cost ASC")
|
||||||
|
fun selectExpensesCost(): Flow<List<Expense>>
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database (
|
||||||
|
entities = [Expense::class],
|
||||||
|
version = 1
|
||||||
|
)
|
||||||
|
abstract class ExpenseDatabase: RoomDatabase() {
|
||||||
|
abstract val dao: ExpenseDao
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.style.TextGeometricTransform
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExpenseDialogAdd(
|
||||||
|
state: ExpenseState,
|
||||||
|
onEvent: (ExpenseEvent) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
onEvent(ExpenseEvent.HideDialog)
|
||||||
|
},
|
||||||
|
title = { Text(text = "Add Expense") },
|
||||||
|
text = {
|
||||||
|
Column (
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
){
|
||||||
|
TextField(
|
||||||
|
value = state.name,
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(ExpenseEvent.SetName(it))
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "Name")
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words)
|
||||||
|
)
|
||||||
|
// TBD: Had to make this a String, can we turn it back to a Double somehow?
|
||||||
|
TextField(
|
||||||
|
value = state.cost,
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(ExpenseEvent.SetCost(it))
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "Cost")
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal)
|
||||||
|
)
|
||||||
|
/* Unsure what to do here yet, a simple Picker type does not seems to exist?
|
||||||
|
TextField(
|
||||||
|
value = state.rate,
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(ExpenseEvent.SetRate(it))
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "Rate")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
onEvent(ExpenseEvent.SaveExpense)
|
||||||
|
}) {
|
||||||
|
Text(text = "Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
sealed interface ExpenseEvent {
|
||||||
|
object SaveExpense: ExpenseEvent
|
||||||
|
data class SetName(val name: String): ExpenseEvent
|
||||||
|
data class SetCost(val cost: String): ExpenseEvent
|
||||||
|
data class SetRate(val rate: Rate): ExpenseEvent
|
||||||
|
object ShowDialog: ExpenseEvent
|
||||||
|
object HideDialog: ExpenseEvent
|
||||||
|
data class SortExpenses(val sortType: SortType): ExpenseEvent
|
||||||
|
data class DeleteExpense(val expense: Expense): ExpenseEvent
|
||||||
|
}
|
126
app/src/main/java/com/hyperling/expensetracker/ExpenseScreen.kt
Normal file
126
app/src/main/java/com/hyperling/expensetracker/ExpenseScreen.kt
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
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.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
fun getEnumStringResourceID(enumName: String): Int {
|
||||||
|
return (
|
||||||
|
when (enumName) {
|
||||||
|
SortType.NAME.toString() -> R.string.NAME
|
||||||
|
SortType.COST.toString() -> R.string.COST
|
||||||
|
SortType.RATE.toString() -> R.string.RATE
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExpenseScreen(
|
||||||
|
state: ExpenseState,
|
||||||
|
onEvent: (ExpenseEvent) -> Unit
|
||||||
|
){
|
||||||
|
Scaffold (
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(onClick = {
|
||||||
|
onEvent(ExpenseEvent.ShowDialog)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = "Add Expense",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
){ padding ->
|
||||||
|
if(state.isAddingExpense){
|
||||||
|
ExpenseDialogAdd(state, onEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn (
|
||||||
|
contentPadding = padding,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
){
|
||||||
|
item {
|
||||||
|
Row (){
|
||||||
|
Text(text = "Sort ascending by:")
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
SortType.values().forEach { sortType ->
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
onEvent(ExpenseEvent.SortExpenses(sortType))
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
RadioButton(
|
||||||
|
selected = state.sortType == sortType,
|
||||||
|
onClick = {
|
||||||
|
onEvent(ExpenseEvent.SortExpenses(sortType))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(text = stringResource(getEnumStringResourceID(sortType.name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row (){
|
||||||
|
Text(text = "Expenses:")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(state.expenses) {expense ->
|
||||||
|
Row (
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text (
|
||||||
|
text = expense.name,
|
||||||
|
fontSize = 20.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "${expense.cost} per ${expense.rate}",
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(onClick = {
|
||||||
|
onEvent(ExpenseEvent.DeleteExpense(expense))
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = "Delete Expense"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
data class ExpenseState(
|
||||||
|
val expenses: List<Expense> = emptyList(),
|
||||||
|
val name: String = "",
|
||||||
|
val cost: String = "",
|
||||||
|
val rate: Rate = Rate.MONTHLY,
|
||||||
|
val isAddingExpense: Boolean = false,
|
||||||
|
val sortType: SortType = SortType.NAME,
|
||||||
|
)
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,136 +1,61 @@
|
|||||||
package com.hyperling.expensetracker
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
import android.content.Context
|
import com.hyperling.expensetracker.ui.theme.ExpenseTrackerTheme
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.hyperling.expensetracker.ui.theme.ExpenseTrackerTheme
|
import androidx.room.Room
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private val _db by lazy {
|
||||||
|
Room.databaseBuilder(
|
||||||
|
applicationContext,
|
||||||
|
ExpenseDatabase::class.java,
|
||||||
|
"expenses.db"
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
private val _viewModel by viewModels<ExpenseViewModel> (
|
||||||
|
factoryProducer = {
|
||||||
|
object : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return ExpenseViewModel(_db.dao) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
ExpenseTrackerTheme {
|
ExpenseTrackerTheme {
|
||||||
Surface {
|
val state by _viewModel.state.collectAsState()
|
||||||
Main()
|
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)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainPreview() {
|
fun MainPreview() {
|
||||||
@ -138,3 +63,4 @@ fun MainPreview() {
|
|||||||
Main()
|
Main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
8
app/src/main/java/com/hyperling/expensetracker/Rate.kt
Normal file
8
app/src/main/java/com/hyperling/expensetracker/Rate.kt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
enum class Rate {
|
||||||
|
DAILY,
|
||||||
|
WEEKLY,
|
||||||
|
MONTHLY,
|
||||||
|
YEARLY,
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.hyperling.expensetracker
|
||||||
|
|
||||||
|
enum class SortType {
|
||||||
|
NAME,
|
||||||
|
COST,
|
||||||
|
RATE,
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Recurring Expenses</string>
|
<string name="app_name">Recurring Expenses</string>
|
||||||
|
<string name="NAME">Name</string>
|
||||||
|
<string name="COST">Cost</string>
|
||||||
|
<string name="RATE">Rate</string>
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user