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.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.sockenklaus.batterytracker.R import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.databinding.FragmentAddBatteryBinding import com.sockenklaus.batterytracker.databinding.FragmentAddBatteryBinding
import com.sockenklaus.batterytracker.room.entities.Battery 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.BatteryTrackerTheme
import com.sockenklaus.batterytracker.ui.theme.Gray500
import com.sockenklaus.batterytracker.util.validateDecimal import com.sockenklaus.batterytracker.util.validateDecimal
/** /**
@@ -62,8 +63,8 @@ class AddBatteryFragment : Fragment() {
onValueChange = { value -> onValueChange = { value ->
model.batteryName = value model.batteryName = value
model.batteryHasError = false model.batteryHasError = false
model.batteryHelperId = null model.batteryHelperId = R.string.helper_required
if(batteries.any{ it.name.lowercase() == value.lowercase() }){ if(batteries.any{ it.name.equals(value, ignoreCase = true) }){
model.batteryHasError = true model.batteryHasError = true
model.batteryHelperId = R.string.helper_battery_not_unique model.batteryHelperId = R.string.helper_battery_not_unique
} }
@@ -80,14 +81,10 @@ class AddBatteryFragment : Fragment() {
value = model.declaredCapacity, value = model.declaredCapacity,
onValueChange = { onValueChange = {
model.declaredCapacity = validateDecimal(it, model.declaredCapacity) model.declaredCapacity = validateDecimal(it, model.declaredCapacity)
model.capacityHasError = false
model.capacityHelperId = null
}, },
labelId = R.string.hint_enter_declared_capacity, labelId = R.string.hint_enter_declared_capacity,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") }, leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") },
isError = model.capacityHasError,
helperTextId = model.capacityHelperId
) )
Spacer(Modifier.size(outerPadding)) Spacer(Modifier.size(outerPadding))
@@ -95,10 +92,9 @@ class AddBatteryFragment : Fragment() {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
onClick = { onClick = {
if(model.batteryName.isBlank()) model.batteryHasError = true if(model.batteryName.isBlank()) model.batteryHasError = true
if(model.declaredCapacity.isBlank()) model.capacityHasError = true
if(!model.batteryHasError && !model.capacityHasError){ if(!model.batteryHasError && model.saveBattery(model.batteryName, model.declaredCapacity)){
model.saveBattery(model.batteryName, model.declaredCapacity) findNavController().navigate(R.id.action_nav_add_battery_to_nav_home)
} }
}, },
icon = { Icon(Icons.Default.Save, "Icon Save") }, icon = { Icon(Icons.Default.Save, "Icon Save") },
@@ -113,39 +109,6 @@ class AddBatteryFragment : Fragment() {
return view 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 { /*companion object {
* Use this factory method to create a new instance of * 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.AndroidViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.BatteryTrackerDB import com.sockenklaus.batterytracker.room.BatteryTrackerDB
import com.sockenklaus.batterytracker.room.entities.Battery import com.sockenklaus.batterytracker.room.entities.Battery
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.lang.Exception
class AddBatteryViewModel(application: Application): AndroidViewModel(application) { class AddBatteryViewModel(application: Application): AndroidViewModel(application) {
private val app = application
private val db = BatteryTrackerDB.getInstance(application) private val db = BatteryTrackerDB.getInstance(application)
val batteries = db.batteryDao().getBatteries().asLiveData() val batteries = db.batteryDao().getBatteries().asLiveData()
var batteryName by mutableStateOf("") var batteryName by mutableStateOf("")
var batteryHasError by mutableStateOf(false) var batteryHasError by mutableStateOf(false)
var batteryHelperId: Int? by mutableStateOf(null) var batteryHelperId by mutableStateOf(R.string.helper_required)
var declaredCapacity by mutableStateOf("") var declaredCapacity by mutableStateOf("")
var capacityHasError by mutableStateOf(false)
var capacityHelperId: Int? by mutableStateOf(null)
fun saveBattery( fun saveBattery(
name: String, name: String,
declaredCapacity: String? = null, declaredCapacity: String,
){ ): Boolean {
val battery = Battery( val battery = Battery(
name = name, name = name,
declaredCapacity = declaredCapacity?.toDouble() declaredCapacity = if(declaredCapacity.isNotBlank()) declaredCapacity.toDouble() else null
) )
viewModelScope.launch(Dispatchers.IO){ return try {
db.batteryDao().insert(battery) 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.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.sockenklaus.batterytracker.R import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.databinding.FragmentAddChargeBinding import com.sockenklaus.batterytracker.databinding.FragmentAddChargeBinding
import com.sockenklaus.batterytracker.room.entities.Battery 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.BatteryTrackerTheme
import com.sockenklaus.batterytracker.ui.theme.Gray500
import com.sockenklaus.batterytracker.util.validateDecimal import com.sockenklaus.batterytracker.util.validateDecimal
import java.time.* import java.time.*
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@@ -36,7 +39,7 @@ import java.util.*
class AddChargeFragment : Fragment() { class AddChargeFragment : Fragment() {
private var _binding: FragmentAddChargeBinding? = null private var _binding: FragmentAddChargeBinding? = null
private lateinit var viewModel: AddChargeViewModel private lateinit var model: AddChargeViewModel
private val binding get() = _binding!! private val binding get() = _binding!!
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@@ -47,11 +50,11 @@ class AddChargeFragment : Fragment() {
): View { ): View {
_binding = FragmentAddChargeBinding.inflate(inflater, container, false) _binding = FragmentAddChargeBinding.inflate(inflater, container, false)
viewModel = ViewModelProvider(this)[AddChargeViewModel::class.java] model = ViewModelProvider(this)[AddChargeViewModel::class.java]
var items = emptyList<Battery>() var batteries = emptyList<Battery>()
viewModel.batteries.observe(this.viewLifecycleOwner){ model.batteries.observe(this.viewLifecycleOwner){
items = it batteries = it
} }
binding.root.setContent{ binding.root.setContent{
@@ -68,19 +71,26 @@ class AddChargeFragment : Fragment() {
expanded = bIdExpanded, expanded = bIdExpanded,
onExpandedChange = { bIdExpanded = !bIdExpanded} onExpandedChange = { bIdExpanded = !bIdExpanded}
) { ) {
val filteringOptions = batteries.filter { it.name.contains(model.batteryId.text, ignoreCase = true)}
OutlinedTextField( OutlinedTextField(
value = viewModel.batteryId, value = model.batteryId,
onValueChange = { viewModel.batteryId = it }, 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)) }, label = { Text(stringResource(R.string.select_battery_id)) },
leadingIcon = { Icon(Icons.Default.Tag, null) }, leadingIcon = { Icon(Icons.Default.Tag, null) },
trailingIcon = { trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = bIdExpanded) ExposedDropdownMenuDefaults.TrailingIcon(expanded = bIdExpanded)
}, },
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), 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()) { if(filteringOptions.isNotEmpty()) {
ExposedDropdownMenu( ExposedDropdownMenu(
expanded = bIdExpanded, expanded = bIdExpanded,
@@ -89,7 +99,7 @@ class AddChargeFragment : Fragment() {
for (filteringOption in filteringOptions) { for (filteringOption in filteringOptions) {
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
viewModel.batteryId = TextFieldValue( model.batteryId = TextFieldValue(
text = filteringOption.name, text = filteringOption.name,
selection = TextRange(filteringOption.name.length) 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)) Spacer(Modifier.size(innerPadding))
OutlinedTextField( MyOutlinedTextField(
value = viewModel.charge, value = model.charge,
onValueChange = { viewModel.charge = validateDecimal(it, viewModel.charge) }, onValueChange = {
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Charging Full") }, model.charge = validateDecimal(it, model.charge)
model.chargeHasError = false
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
label = { Text(stringResource(R.string.hint_charge)) }, labelId = R.string.hint_charge,
modifier = Modifier.fillMaxWidth() leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Chargin Full") },
isError = model.chargeHasError,
helperTextId = R.string.helper_required
) )
Spacer(Modifier.size(innerPadding)) Spacer(Modifier.size(innerPadding))
ChargeDatePicker( ChargeDatePicker(
date = viewModel.date, date = model.date,
onSelect = { viewModel.date = it} onSelect = { model.date = it}
) )
Spacer(Modifier.size(outerPadding)) Spacer(Modifier.size(outerPadding))
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.button_save_charge)) }, 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") }, icon = { Icon(Icons.Default.Save, "Icon Save") },
modifier = Modifier.align(Alignment.End) modifier = Modifier.align(Alignment.End)
@@ -137,11 +173,6 @@ class AddChargeFragment : Fragment() {
return binding.root return binding.root
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@Composable @Composable
fun ChargeDatePicker( fun ChargeDatePicker(
date: LocalDateTime, 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.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope
import androidx.room.Room import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.BatteryTrackerDB import com.sockenklaus.batterytracker.room.BatteryTrackerDB
import com.sockenklaus.batterytracker.room.entities.Battery 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 import java.time.LocalDateTime
class AddChargeViewModel(application: Application) : AndroidViewModel(application) { class AddChargeViewModel(application: Application) : AndroidViewModel(application) {
@@ -19,11 +22,43 @@ class AddChargeViewModel(application: Application) : AndroidViewModel(applicatio
private val db = BatteryTrackerDB.getInstance(application) private val db = BatteryTrackerDB.getInstance(application)
var batteries: LiveData<List<Battery>> = db.batteryDao().getBatteries().asLiveData() 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 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:id="@+id/nav_add_charge"
android:name="com.sockenklaus.batterytracker.ui.fragments.add_charge.AddChargeFragment" android:name="com.sockenklaus.batterytracker.ui.fragments.add_charge.AddChargeFragment"
android:label="@string/menu_add_charge" 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 <fragment
android:id="@+id/nav_add_battery" android:id="@+id/nav_add_battery"
android:name="com.sockenklaus.batterytracker.ui.fragments.add_battery.AddBatteryFragment" android:name="com.sockenklaus.batterytracker.ui.fragments.add_battery.AddBatteryFragment"
android:label="@string/menu_add_battery" 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 <fragment
android:id="@+id/nav_settings" android:id="@+id/nav_settings"

View File

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