Implemented AddChargeFragment.kt functionality

This commit is contained in:
sockenklaus
2022-07-19 22:23:19 +02:00
parent c819c90269
commit f5e3d51378
7 changed files with 220 additions and 84 deletions

View File

@@ -17,11 +17,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.databinding.FragmentAddBatteryBinding
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.fragments.composables.MyOutlinedTextField
import com.sockenklaus.batterytracker.ui.theme.BatteryTrackerTheme
import com.sockenklaus.batterytracker.ui.theme.Gray500
import com.sockenklaus.batterytracker.util.validateDecimal
/**
@@ -62,8 +63,8 @@ class AddBatteryFragment : Fragment() {
onValueChange = { value ->
model.batteryName = value
model.batteryHasError = false
model.batteryHelperId = null
if(batteries.any{ it.name.lowercase() == value.lowercase() }){
model.batteryHelperId = R.string.helper_required
if(batteries.any{ it.name.equals(value, ignoreCase = true) }){
model.batteryHasError = true
model.batteryHelperId = R.string.helper_battery_not_unique
}
@@ -80,14 +81,10 @@ class AddBatteryFragment : Fragment() {
value = model.declaredCapacity,
onValueChange = {
model.declaredCapacity = validateDecimal(it, model.declaredCapacity)
model.capacityHasError = false
model.capacityHelperId = null
},
labelId = R.string.hint_enter_declared_capacity,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") },
isError = model.capacityHasError,
helperTextId = model.capacityHelperId
)
Spacer(Modifier.size(outerPadding))
@@ -95,10 +92,9 @@ class AddBatteryFragment : Fragment() {
ExtendedFloatingActionButton(
onClick = {
if(model.batteryName.isBlank()) model.batteryHasError = true
if(model.declaredCapacity.isBlank()) model.capacityHasError = true
if(!model.batteryHasError && !model.capacityHasError){
model.saveBattery(model.batteryName, model.declaredCapacity)
if(!model.batteryHasError && model.saveBattery(model.batteryName, model.declaredCapacity)){
findNavController().navigate(R.id.action_nav_add_battery_to_nav_home)
}
},
icon = { Icon(Icons.Default.Save, "Icon Save") },
@@ -113,39 +109,6 @@ class AddBatteryFragment : Fragment() {
return view
}
@Composable
fun MyOutlinedTextField(
value: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
labelId: Int,
leadingIcon: (@Composable () -> Unit)? = null,
isError: Boolean = false,
helperTextId: Int? = null
) {
val helperTextColor = if(isError){
MaterialTheme.colors.error
} else {
Gray500
}
OutlinedTextField(
value = value,
onValueChange = onValueChange,
keyboardOptions = keyboardOptions,
label = { Text(stringResource(labelId)) },
leadingIcon = leadingIcon,
isError = isError,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(helperTextId ?: R.string.helper_required),
style = MaterialTheme.typography.caption,
color = helperTextColor,
modifier = Modifier.padding(start = 16.dp)
)
}
/*companion object {
* Use this factory method to create a new instance of

View File

@@ -7,36 +7,42 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.BatteryTrackerDB
import com.sockenklaus.batterytracker.room.entities.Battery
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.lang.Exception
class AddBatteryViewModel(application: Application): AndroidViewModel(application) {
private val app = application
private val db = BatteryTrackerDB.getInstance(application)
val batteries = db.batteryDao().getBatteries().asLiveData()
var batteryName by mutableStateOf("")
var batteryHasError by mutableStateOf(false)
var batteryHelperId: Int? by mutableStateOf(null)
var batteryHelperId by mutableStateOf(R.string.helper_required)
var declaredCapacity by mutableStateOf("")
var capacityHasError by mutableStateOf(false)
var capacityHelperId: Int? by mutableStateOf(null)
fun saveBattery(
name: String,
declaredCapacity: String? = null,
){
declaredCapacity: String,
): Boolean {
val battery = Battery(
name = name,
declaredCapacity = declaredCapacity?.toDouble()
declaredCapacity = if(declaredCapacity.isNotBlank()) declaredCapacity.toDouble() else null
)
viewModelScope.launch(Dispatchers.IO){
db.batteryDao().insert(battery)
return try {
viewModelScope.launch(Dispatchers.IO){
db.batteryDao().insert(battery)
}
true
} catch(e: Exception){
println(e.message)
false
}
}
}

View File

@@ -22,11 +22,14 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import com.google.android.material.datepicker.MaterialDatePicker
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.databinding.FragmentAddChargeBinding
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.fragments.composables.MyOutlinedTextField
import com.sockenklaus.batterytracker.ui.theme.BatteryTrackerTheme
import com.sockenklaus.batterytracker.ui.theme.Gray500
import com.sockenklaus.batterytracker.util.validateDecimal
import java.time.*
import java.time.format.DateTimeFormatter
@@ -36,7 +39,7 @@ import java.util.*
class AddChargeFragment : Fragment() {
private var _binding: FragmentAddChargeBinding? = null
private lateinit var viewModel: AddChargeViewModel
private lateinit var model: AddChargeViewModel
private val binding get() = _binding!!
@OptIn(ExperimentalMaterialApi::class)
@@ -47,11 +50,11 @@ class AddChargeFragment : Fragment() {
): View {
_binding = FragmentAddChargeBinding.inflate(inflater, container, false)
viewModel = ViewModelProvider(this)[AddChargeViewModel::class.java]
var items = emptyList<Battery>()
model = ViewModelProvider(this)[AddChargeViewModel::class.java]
var batteries = emptyList<Battery>()
viewModel.batteries.observe(this.viewLifecycleOwner){
items = it
model.batteries.observe(this.viewLifecycleOwner){
batteries = it
}
binding.root.setContent{
@@ -68,19 +71,26 @@ class AddChargeFragment : Fragment() {
expanded = bIdExpanded,
onExpandedChange = { bIdExpanded = !bIdExpanded}
) {
val filteringOptions = batteries.filter { it.name.contains(model.batteryId.text, ignoreCase = true)}
OutlinedTextField(
value = viewModel.batteryId,
onValueChange = { viewModel.batteryId = it },
value = model.batteryId,
onValueChange = {
model.batteryId = it
model.batteryHasError = false
model.batteryHelper = R.string.helper_required
bIdExpanded = filteringOptions.size > 1
},
label = { Text(stringResource(R.string.select_battery_id)) },
leadingIcon = { Icon(Icons.Default.Tag, null) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = bIdExpanded)
},
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
isError = model.batteryHasError
)
val filteringOptions = items.filter { it.name.contains(viewModel.batteryId.text, ignoreCase = true)}
if(filteringOptions.size > 1 && viewModel.batteryId.text.isNotBlank()) bIdExpanded = true
if(filteringOptions.isNotEmpty()) {
ExposedDropdownMenu(
expanded = bIdExpanded,
@@ -89,7 +99,7 @@ class AddChargeFragment : Fragment() {
for (filteringOption in filteringOptions) {
DropdownMenuItem(
onClick = {
viewModel.batteryId = TextFieldValue(
model.batteryId = TextFieldValue(
text = filteringOption.name,
selection = TextRange(filteringOption.name.length)
)
@@ -102,30 +112,56 @@ class AddChargeFragment : Fragment() {
}
}
}
Text(
text = stringResource(model.batteryHelper),
style = MaterialTheme.typography.caption,
color = if(model.batteryHasError) MaterialTheme.colors.error else Gray500,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(Modifier.size(innerPadding))
OutlinedTextField(
value = viewModel.charge,
onValueChange = { viewModel.charge = validateDecimal(it, viewModel.charge) },
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Charging Full") },
MyOutlinedTextField(
value = model.charge,
onValueChange = {
model.charge = validateDecimal(it, model.charge)
model.chargeHasError = false
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
label = { Text(stringResource(R.string.hint_charge)) },
modifier = Modifier.fillMaxWidth()
labelId = R.string.hint_charge,
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Chargin Full") },
isError = model.chargeHasError,
helperTextId = R.string.helper_required
)
Spacer(Modifier.size(innerPadding))
ChargeDatePicker(
date = viewModel.date,
onSelect = { viewModel.date = it}
date = model.date,
onSelect = { model.date = it}
)
Spacer(Modifier.size(outerPadding))
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.button_save_charge)) },
onClick = { viewModel.saveCharge() },
onClick = {
if(!batteries.any{ it.name == model.batteryId.text }){
model.batteryHasError = true
model.batteryHelper = R.string.helper_battery_not_found
}
if(model.batteryId.text.isBlank()){
model.batteryHasError = true
model.batteryHelper = R.string.helper_required
}
if(model.charge.isBlank()){
model.chargeHasError = true
}
if(!model.batteryHasError && !model.chargeHasError && model.saveCharge(batteries, model.batteryId.text, model.charge, model.date)){
findNavController().navigate(R.id.action_nav_add_charge_to_nav_home)
}
},
icon = { Icon(Icons.Default.Save, "Icon Save") },
modifier = Modifier.align(Alignment.End)
@@ -137,11 +173,6 @@ class AddChargeFragment : Fragment() {
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@Composable
fun ChargeDatePicker(
date: LocalDateTime,
@@ -174,4 +205,8 @@ class AddChargeFragment : Fragment() {
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -8,10 +8,13 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.MutableLiveData
import androidx.room.Room
import androidx.lifecycle.viewModelScope
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.BatteryTrackerDB
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.room.entities.Charge
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.time.LocalDateTime
class AddChargeViewModel(application: Application) : AndroidViewModel(application) {
@@ -19,11 +22,43 @@ class AddChargeViewModel(application: Application) : AndroidViewModel(applicatio
private val db = BatteryTrackerDB.getInstance(application)
var batteries: LiveData<List<Battery>> = db.batteryDao().getBatteries().asLiveData()
var batteryId: TextFieldValue by mutableStateOf(TextFieldValue(""))
var batteryId by mutableStateOf(TextFieldValue(""))
var date: LocalDateTime by mutableStateOf(LocalDateTime.now())
var charge: String by mutableStateOf("")
var charge by mutableStateOf("")
fun saveCharge() {
var batteryHasError by mutableStateOf(false)
var batteryHelper by mutableStateOf(R.string.helper_required)
var chargeHasError by mutableStateOf(false)
fun saveCharge(
batteryList: List<Battery>,
batteryName: String,
charge: String,
date: LocalDateTime
):Boolean {
val battery = batteryList.find { it.name == batteryName }
if (battery != null) {
return try {
viewModelScope.launch(Dispatchers.IO){
db.chargeDao().insert(
Charge(
batteryId = battery.id,
charge = charge.toDouble(),
date = date
)
)
}
true
} catch(e: Exception){
println(e.message)
false
}
}
println("Error in saveCharge")
return false
}
}

View File

@@ -0,0 +1,88 @@
package com.sockenklaus.batterytracker.ui.fragments.composables
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.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.sockenklaus.batterytracker.ui.theme.Gray500
@Composable
fun MyOutlinedTextField(
value: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
labelId: Int,
leadingIcon: (@Composable () -> Unit)? = null,
trailingIcon: (@Composable () -> Unit)? = null,
isError: Boolean = false,
helperTextId: Int? = null,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
val helperTextColor = if(isError){
MaterialTheme.colors.error
} else {
Gray500
}
OutlinedTextField(
value = value,
onValueChange = onValueChange,
keyboardOptions = keyboardOptions,
label = { Text(stringResource(labelId)) },
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
isError = isError,
modifier = Modifier.fillMaxWidth(),
colors = colors
)
Text(
text = if(helperTextId != null) stringResource(helperTextId) else "",
style = MaterialTheme.typography.caption,
color = helperTextColor,
modifier = Modifier.padding(start = 16.dp)
)
}
@Composable
fun MyOutlinedTextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
labelId: Int,
leadingIcon: (@Composable () -> Unit)? = null,
trailingIcon: (@Composable () -> Unit)? = null,
isError: Boolean = false,
helperTextId: Int? = null,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
val helperTextColor = if(isError){
MaterialTheme.colors.error
} else {
Gray500
}
Column() {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
keyboardOptions = keyboardOptions,
label = { Text(stringResource(labelId)) },
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
isError = isError,
modifier = Modifier.fillMaxWidth(),
colors = colors
)
Text(
text = if(helperTextId != null) stringResource(helperTextId) else "",
style = MaterialTheme.typography.caption,
color = helperTextColor,
modifier = Modifier.padding(start = 16.dp)
)
}
}

View File

@@ -15,13 +15,21 @@
android:id="@+id/nav_add_charge"
android:name="com.sockenklaus.batterytracker.ui.fragments.add_charge.AddChargeFragment"
android:label="@string/menu_add_charge"
tools:layout="@layout/fragment_add_charge" />
tools:layout="@layout/fragment_add_charge" >
<action
android:id="@+id/action_nav_add_charge_to_nav_home"
app:destination="@id/nav_home" />
</fragment>
<fragment
android:id="@+id/nav_add_battery"
android:name="com.sockenklaus.batterytracker.ui.fragments.add_battery.AddBatteryFragment"
android:label="@string/menu_add_battery"
tools:layout="@layout/fragment_add_battery" />
tools:layout="@layout/fragment_add_battery" >
<action
android:id="@+id/action_nav_add_battery_to_nav_home"
app:destination="@id/nav_home" />
</fragment>
<fragment
android:id="@+id/nav_settings"

View File

@@ -29,4 +29,5 @@
<string name="title_activity_add_battery">AddBattery</string>
<string name="helper_required">* Required</string>
<string name="helper_battery_not_unique">Battery-Name not unique!</string>
<string name="helper_battery_not_found">Battery not found</string>
</resources>