init
This commit is contained in:
@@ -2,6 +2,7 @@ package cc.n0th1ng.tripmoney.data.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
@@ -19,4 +20,6 @@ interface TripDao {
|
||||
)
|
||||
fun tripsPaged(): PagingSource<Int, Trip>
|
||||
|
||||
@Delete
|
||||
suspend fun delete(trip: Trip)
|
||||
}
|
||||
|
||||
@@ -22,4 +22,9 @@ class TripRepository @Inject constructor(private val tripDao: TripDao) {
|
||||
pagingSourceFactory = { tripDao.tripsPaged() }
|
||||
).flow
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
suspend fun delete(trip: Trip) {
|
||||
tripDao.delete(trip)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import cc.n0th1ng.tripmoney.R
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||
@@ -46,9 +47,11 @@ import cc.n0th1ng.tripmoney.screens.listexpense.CategorySelectionDialog
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.CurrencySelectionDialog
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.DateTimePicker
|
||||
import cc.n0th1ng.tripmoney.theme.TripMoneyTheme
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -57,11 +60,10 @@ import java.time.LocalDateTime
|
||||
fun AddExpenseBottomSheet(
|
||||
onSave: (Expense) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
categories: List<Category>,
|
||||
expenseAndCategoryViewModel: ExpenseAndCategoryViewModel,
|
||||
expenseDtoToEdit: ExpenseDto?
|
||||
) {
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||
val currentTripId by settingsViewModel.currentTrip.collectAsState()
|
||||
var amount by remember {
|
||||
mutableStateOf(
|
||||
@@ -70,11 +72,18 @@ fun AddExpenseBottomSheet(
|
||||
}
|
||||
var showCurrencyDialog by remember { mutableStateOf(false) }
|
||||
var showCategoryDialog by remember { mutableStateOf(false) }
|
||||
var currency by remember { mutableStateOf(expenseDtoToEdit?.expense?.currency ?: "PLN") }
|
||||
var showDateTimePicker by remember { mutableStateOf(false) }
|
||||
var currency by remember {
|
||||
mutableStateOf(
|
||||
expenseDtoToEdit?.expense?.currency ?: Currencies.PLN.name
|
||||
)
|
||||
}
|
||||
var category by remember { mutableStateOf(expenseDtoToEdit?.category ?: categories[0]) }
|
||||
var datetime by remember {
|
||||
mutableStateOf(
|
||||
LocalDateTime.parse(expenseDtoToEdit?.expense?.datetime ?: LocalDateTime.now().toString())
|
||||
LocalDateTime.parse(
|
||||
expenseDtoToEdit?.expense?.datetime ?: LocalDateTime.now().toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
var note by remember { mutableStateOf(expenseDtoToEdit?.expense?.note ?: "") }
|
||||
@@ -103,10 +112,12 @@ fun AddExpenseBottomSheet(
|
||||
CurrencyButton(onClick = { showCurrencyDialog = true }, text = currency)
|
||||
}
|
||||
Spacer(Modifier.height(14.dp))
|
||||
DateTimePicker(
|
||||
dateTime = datetime,
|
||||
onChange = { datetime = it }
|
||||
)
|
||||
OutlinedButton(onClick = { showDateTimePicker = true }) {
|
||||
Text(
|
||||
text = datetime.format(DateTimeFormatter.ofPattern("dd.MM HH:mm")),
|
||||
fontSize = 17.sp
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(14.dp))
|
||||
CategoryButton(onClick = { showCategoryDialog = true }, category = category)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
@@ -155,7 +166,12 @@ fun AddExpenseBottomSheet(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showDateTimePicker) {
|
||||
DateTimePicker(datetime, onChange = { newDateTime ->
|
||||
datetime = newDateTime
|
||||
showDateTimePicker = false
|
||||
})
|
||||
}
|
||||
|
||||
if (showCurrencyDialog) {
|
||||
CurrencySelectionDialog(
|
||||
@@ -164,8 +180,7 @@ fun AddExpenseBottomSheet(
|
||||
showCurrencyDialog = false
|
||||
currency = selectedCurrency
|
||||
},
|
||||
selected = currency,
|
||||
listOfCurrencies = listOf("PLN", "EUR", "USD")
|
||||
selected = currency
|
||||
)
|
||||
}
|
||||
|
||||
@@ -177,8 +192,7 @@ fun AddExpenseBottomSheet(
|
||||
category = selectedCategory
|
||||
},
|
||||
selected = category,
|
||||
categories = categories,
|
||||
settingsAndCategoryViewModel = expenseAndCategoryViewModel
|
||||
categories = categories
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import cc.n0th1ng.tripmoney.R.*
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.screens.AddCategoryDialog
|
||||
@@ -36,9 +37,9 @@ fun CategorySelectionDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onCategorySelected: (Category) -> Unit,
|
||||
selected: Category,
|
||||
categories: List<Category>,
|
||||
settingsAndCategoryViewModel: ExpenseAndCategoryViewModel
|
||||
categories: List<Category>
|
||||
) {
|
||||
val expenseAndCategoryViewModel: ExpenseAndCategoryViewModel = hiltViewModel()
|
||||
val listState = rememberLazyListState()
|
||||
var showAddCategoryDialog by remember { mutableStateOf(false) }
|
||||
AlertDialog(
|
||||
@@ -99,7 +100,7 @@ fun CategorySelectionDialog(
|
||||
AddCategoryDialog(onDismiss = {
|
||||
showAddCategoryDialog = false
|
||||
}, onSave = { category ->
|
||||
settingsAndCategoryViewModel.save(category)
|
||||
expenseAndCategoryViewModel.save(category)
|
||||
showAddCategoryDialog = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,20 +14,20 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cc.n0th1ng.tripmoney.R
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
|
||||
@Composable
|
||||
fun CurrencySelectionDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onCurrencySelected: (String) -> Unit,
|
||||
selected: String,
|
||||
listOfCurrencies: List<String>
|
||||
selected: String
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.pick_currency)) },
|
||||
text = {
|
||||
Column {
|
||||
listOfCurrencies.forEach { currency ->
|
||||
Currencies.names().forEach { currency ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
@@ -5,11 +5,13 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.DatePickerState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TimePicker
|
||||
import androidx.compose.material3.TimePickerState
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.material3.rememberTimePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -20,11 +22,73 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.sp
|
||||
import cc.n0th1ng.tripmoney.R.*
|
||||
import java.sql.Time
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Calendar
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DatePicker(
|
||||
dateTime: LocalDate = LocalDate.now(),
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (LocalDate) -> Unit
|
||||
) {
|
||||
val datePickerState =
|
||||
rememberDatePickerState(initialSelectedDateMillis = dateTime.toEpochMilli())
|
||||
|
||||
DatePickerDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val selectedMillis = datePickerState.selectedDateMillis
|
||||
if (selectedMillis != null) {
|
||||
val selectedDate = Instant.ofEpochMilli(selectedMillis)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDate()
|
||||
onConfirm(selectedDate)
|
||||
}
|
||||
}) {
|
||||
Text("OK")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) }
|
||||
}
|
||||
) {
|
||||
DatePicker(state = datePickerState)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TimePicker(onDismiss: () -> Unit, onConfirm: (TimePickerState) -> Unit) {
|
||||
val currentTime = Calendar.getInstance()
|
||||
val timePickerState = rememberTimePickerState(
|
||||
initialHour = currentTime.get(Calendar.HOUR_OF_DAY),
|
||||
initialMinute = currentTime.get(Calendar.MINUTE),
|
||||
is24Hour = true
|
||||
)
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onConfirm(timePickerState) }) {
|
||||
Text("OK")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) }
|
||||
},
|
||||
text = { TimePicker(state = timePickerState) }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@@ -33,76 +97,34 @@ fun DateTimePicker(
|
||||
dateTime: LocalDateTime = LocalDateTime.now(),
|
||||
onChange: (LocalDateTime) -> Unit
|
||||
) {
|
||||
val datePickerState =
|
||||
rememberDatePickerState(initialSelectedDateMillis = dateTime.toEpochMilli())
|
||||
val timePickerState = rememberTimePickerState(
|
||||
initialHour = dateTime.hour,
|
||||
initialMinute = dateTime.minute
|
||||
)
|
||||
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
var showDatePicker by remember { mutableStateOf(true) }
|
||||
var showTimePicker by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
val formatter = DateTimeFormatter.ofPattern("dd.MM HH:mm")
|
||||
OutlinedButton(onClick = { showDatePicker = true }) {
|
||||
Text(text = dateTime.format(formatter), fontSize = 17.sp)
|
||||
}
|
||||
var date by remember { mutableStateOf(dateTime.toLocalDate()) }
|
||||
|
||||
if (showDatePicker) {
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showDatePicker = false },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showDatePicker = false
|
||||
val selectedMillis = datePickerState.selectedDateMillis
|
||||
if (selectedMillis != null) {
|
||||
val selectedDate = Instant.ofEpochMilli(selectedMillis)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDate()
|
||||
// open time picker next
|
||||
showTimePicker = true
|
||||
onChange(
|
||||
LocalDateTime.of(
|
||||
selectedDate,
|
||||
dateTime.toLocalTime()
|
||||
)
|
||||
)
|
||||
}
|
||||
}) {
|
||||
Text("OK")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {
|
||||
showDatePicker = false
|
||||
}) { Text(stringResource(string.cancel)) }
|
||||
}
|
||||
) {
|
||||
DatePicker(state = datePickerState)
|
||||
}
|
||||
DatePicker(onDismiss = { showDatePicker = false }, onConfirm = { newDate ->
|
||||
date = newDate
|
||||
})
|
||||
}
|
||||
|
||||
if (showTimePicker) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showTimePicker = false },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showTimePicker = false
|
||||
val newTime = LocalTime.of(timePickerState.hour, timePickerState.minute)
|
||||
onChange(LocalDateTime.of(dateTime.toLocalDate(), newTime))
|
||||
}) {
|
||||
Text("OK")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showTimePicker = false }) { Text(stringResource(string.cancel)) }
|
||||
},
|
||||
text = { TimePicker(state = timePickerState) }
|
||||
)
|
||||
TimePicker(onDismiss = {
|
||||
showTimePicker = false
|
||||
showDatePicker = true
|
||||
}, onConfirm = { timePickerState ->
|
||||
showTimePicker = false
|
||||
showDatePicker = true
|
||||
val newTime = LocalTime.of(timePickerState.hour, timePickerState.minute)
|
||||
onChange(LocalDateTime.of(date, newTime))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun LocalDateTime.toEpochMilli(): Long =
|
||||
this.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||||
this.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun LocalDate.toEpochMilli(): Long =
|
||||
this.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||||
@@ -2,7 +2,6 @@ package cc.n0th1ng.tripmoney.screens.listexpense
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -53,7 +52,7 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import cc.n0th1ng.tripmoney.R.*
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.AddExpenseBottomSheet
|
||||
@@ -61,7 +60,6 @@ import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.getValue
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -145,9 +143,7 @@ fun ListExpenseScreen() {
|
||||
expenseDtoToEdit = null
|
||||
showBottomSheet = false
|
||||
},
|
||||
settingsViewModel = settingsViewModel,
|
||||
categories = categories,
|
||||
expenseAndCategoryViewModel = expenseAndCategoryViewModel,
|
||||
expenseDtoToEdit = expenseDtoToEdit
|
||||
)
|
||||
}
|
||||
@@ -287,8 +283,8 @@ fun ExpenseCard(expenseDto: ExpenseDto, onClick: (ExpenseDto) -> Unit) {
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Column(
|
||||
) {
|
||||
Column()
|
||||
{
|
||||
Text(
|
||||
text = expenseDto.category.name,
|
||||
fontWeight = FontWeight.Bold,
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package cc.n0th1ng.tripmoney.screens.trippicker
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import cc.n0th1ng.tripmoney.R
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.CurrencyButton
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.isDoubleTwoDigitsAboveZero
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.CurrencySelectionDialog
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.DatePicker
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.DateTimePicker
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddTripBottomSheet(onDismiss: () -> Unit, onSave: (Trip) -> Unit, tripToEdit: Trip?) {
|
||||
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
var name by remember { mutableStateOf(tripToEdit?.name ?: "") }
|
||||
var startDate by remember {
|
||||
mutableStateOf(
|
||||
LocalDate.parse(tripToEdit?.startDate ?: LocalDate.now().toString())
|
||||
)
|
||||
}
|
||||
var showCurrencyDialog by remember { mutableStateOf(false) }
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
var currency by remember { mutableStateOf(tripToEdit?.currency ?: Currencies.default().name) }
|
||||
var enableSave by remember { mutableStateOf(tripToEdit != null) }
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
NameInput(name = name, onTextChange = { newText ->
|
||||
name = newText
|
||||
enableSave = !name.isEmpty()
|
||||
})
|
||||
CurrencyButton(onClick = {showCurrencyDialog = true}, currency)
|
||||
OutlinedButton(onClick = { showDatePicker = true }) {
|
||||
Text(
|
||||
text = startDate.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")),
|
||||
fontSize = 17.sp
|
||||
)
|
||||
}
|
||||
OutlinedButton(
|
||||
enabled = enableSave,
|
||||
onClick = {
|
||||
onSave(Trip(name = name, startDate = startDate.toString(), currency = currency))
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = stringResource(R.string.save)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showCurrencyDialog) {
|
||||
CurrencySelectionDialog(
|
||||
onDismiss = { showCurrencyDialog = false },
|
||||
onCurrencySelected = { selectedCurrency ->
|
||||
showCurrencyDialog = false
|
||||
currency = selectedCurrency
|
||||
},
|
||||
selected = currency
|
||||
)
|
||||
}
|
||||
|
||||
if (showDatePicker) {
|
||||
DatePicker(startDate, onDismiss = {showDatePicker = false}, onConfirm = { newDate ->
|
||||
startDate = newDate
|
||||
showDatePicker = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NameInput(name: String, onTextChange: (String) -> Unit) {
|
||||
var text by remember { mutableStateOf(name) }
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.name)) }, value = name, onValueChange = { newText ->
|
||||
text = newText
|
||||
onTextChange(text)
|
||||
}, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package cc.n0th1ng.tripmoney.screens.trippicker
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -7,74 +11,158 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SwipeToDismissBox
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
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.draw.alpha
|
||||
import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import cc.n0th1ng.tripmoney.R.string
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.navigation.Screens
|
||||
import cc.n0th1ng.tripmoney.screens.addexpense.AddExpenseBottomSheet
|
||||
import cc.n0th1ng.tripmoney.screens.listexpense.DeleteConfirmationDialog
|
||||
import cc.n0th1ng.tripmoney.viewmodel.SettingsViewModel
|
||||
import cc.n0th1ng.tripmoney.viewmodel.TripViewModel
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
fun TripPickerScreen(
|
||||
navController: NavController
|
||||
) {
|
||||
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||
val tripViewModel: TripViewModel = hiltViewModel()
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
val trips: LazyPagingItems<Trip> = tripViewModel.getTrips().collectAsLazyPagingItems()
|
||||
val currentTripId by settingsViewModel.currentTrip.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 15.dp)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
||||
items(trips.itemCount, trips.itemKey { it.id }) { i ->
|
||||
Spacer(Modifier.height(10.dp))
|
||||
val trip = trips[i]
|
||||
if (trip != null) {
|
||||
TripCard(trip, currentTripId == trip.id, onClick = {
|
||||
settingsViewModel.setCurrentTrip(trip.id)
|
||||
navController.navigate(Screens.LIST_EXPENSE)
|
||||
})
|
||||
Scaffold(floatingActionButtonPosition = FabPosition.EndOverlay, floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = { showBottomSheet = true }) {
|
||||
Icon(Icons.Filled.Add, stringResource(string.add_trip))
|
||||
}
|
||||
}) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 15.dp)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
items(trips.itemCount, trips.itemKey { it.id }) { i ->
|
||||
Spacer(Modifier.height(10.dp))
|
||||
val trip = trips[i]
|
||||
if (trip != null) {
|
||||
SwipeToDeleteTripCard(trip, onDelete = {
|
||||
tripViewModel.delete(trip)
|
||||
}, onClick = {
|
||||
settingsViewModel.setCurrentTrip(trip.id)
|
||||
navController.navigate(Screens.LIST_EXPENSE)
|
||||
}, isSelected = currentTripId == trip.id)
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
if (showBottomSheet) {
|
||||
AddTripBottomSheet(
|
||||
onDismiss = {
|
||||
showBottomSheet = false
|
||||
},
|
||||
onSave = { trip ->
|
||||
tripViewModel.save(trip)
|
||||
showBottomSheet = false
|
||||
},
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun TripCard(trip: Trip, isSelected: Boolean, onClick: () -> Unit) {
|
||||
fun SwipeToDeleteTripCard(
|
||||
trip: Trip, onDelete: (Trip) -> Unit, onClick: (Trip) -> Unit, isSelected: Boolean
|
||||
) {
|
||||
var dismissed by remember { mutableStateOf(false) }
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (!dismissed) {
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = { dismissValue ->
|
||||
if (dismissValue == SwipeToDismissBoxValue.EndToStart) {
|
||||
showDialog = true
|
||||
false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
if (showDialog) {
|
||||
DeleteConfirmationDialog(onConfirm = {
|
||||
showDialog = false
|
||||
dismissed = true
|
||||
onDelete(trip)
|
||||
}, onCancel = { showDialog = false })
|
||||
}
|
||||
|
||||
SwipeToDismissBox(
|
||||
modifier = Modifier.alpha(if (isSelected) 1.0f else 0.7f),
|
||||
state = dismissState,
|
||||
enableDismissFromStartToEnd = false,
|
||||
backgroundContent = {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(CardDefaults.elevatedShape)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.onError)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Icon(Icons.Default.Delete, contentDescription = stringResource(string.delete))
|
||||
}
|
||||
}) {
|
||||
TripCard(trip, isSelected, onClick = onClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun TripCard(trip: Trip, isSelected: Boolean, onClick: (Trip) -> Unit) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier
|
||||
.height(100.dp)
|
||||
.clickable(true, onClick = onClick)
|
||||
.alpha(if (isSelected) 1.0f else 0.7f),
|
||||
.clickable(true, onClick = { onClick(trip) }),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = if (isSelected) 7.dp else 0.dp)
|
||||
) {
|
||||
Row(
|
||||
@@ -83,8 +171,7 @@ fun TripCard(trip: Trip, isSelected: Boolean, onClick: () -> Unit) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(fontSize = 25.sp, fontWeight = FontWeight.SemiBold, text = trip.name)
|
||||
Text(trip.startDate)
|
||||
@@ -96,6 +183,5 @@ fun TripCard(trip: Trip, isSelected: Boolean, onClick: () -> Unit) {
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
16
app/src/main/java/cc/n0th1ng/tripmoney/utils/Currencies.kt
Normal file
16
app/src/main/java/cc/n0th1ng/tripmoney/utils/Currencies.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package cc.n0th1ng.tripmoney.utils
|
||||
|
||||
enum class Currencies {
|
||||
PLN,
|
||||
EUR,
|
||||
USD;
|
||||
|
||||
companion object {
|
||||
fun default(): Currencies {
|
||||
return PLN
|
||||
}
|
||||
fun names(): List<String> {
|
||||
return Currencies.entries.map { it.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.data.repository.TripRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -16,4 +18,16 @@ class TripViewModel @Inject constructor(private val repository: TripRepository)
|
||||
|
||||
fun getTrips(): Flow<PagingData<Trip>> = repository.getTrips().cachedIn(viewModelScope)
|
||||
|
||||
fun delete(trip: Trip) {
|
||||
viewModelScope.launch {
|
||||
repository.delete(trip)
|
||||
}
|
||||
}
|
||||
|
||||
fun save(trip: Trip) {
|
||||
viewModelScope.launch {
|
||||
repository.save(trip)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,4 +16,6 @@
|
||||
<string name="system_settings">Zgodnie z systemem</string>
|
||||
<string name="theme">Motyw</string>
|
||||
<string name="dark_theme">Ciemny motyw</string>
|
||||
<string name="add_trip">Dodaj Wycieczkę</string>
|
||||
<string name="name">Nazwa</string>
|
||||
</resources>
|
||||
@@ -16,5 +16,6 @@
|
||||
<string name="light_theme">Light theme</string>
|
||||
<string name="pick_theme">Pick a theme</string>
|
||||
<string name="system_settings">System settings</string>
|
||||
<!-- <string name="theme">Theme</string>-->
|
||||
<string name="add_trip">Add Trip</string>
|
||||
<string name="name">Name</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user