Several changes to implement more streamlined UX.

This commit is contained in:
Sockenklaus
2022-11-19 00:03:02 +01:00
parent a7c92b020b
commit cd1ce2ccda
9 changed files with 138 additions and 80 deletions

View File

@@ -41,6 +41,8 @@ android {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
applicationIdSuffix '.release'
versionNameSuffix 'release'
} }
} }
compileOptions { compileOptions {
@@ -64,6 +66,7 @@ android {
} }
} }
namespace 'com.sockenklaus.batterytracker' namespace 'com.sockenklaus.batterytracker'
flavorDimensions
} }
dependencies { dependencies {

View File

@@ -26,6 +26,6 @@ interface ChargeDao {
@Query("SELECT * FROM batteries WHERE id = :id") @Query("SELECT * FROM batteries WHERE id = :id")
fun getBatteryAndCharges(id: Int): Flow<BatteryAndCharges> fun getBatteryAndCharges(id: Int): Flow<BatteryAndCharges>
@Query("SELECT * FROM charges WHERE battery_id = :id ORDER BY date DESC") @Query("SELECT * FROM charges WHERE battery_id = :id ORDER BY created_at DESC")
fun getChargesByBatteryId(id: Int): Flow<List<Charge>> fun getChargesByBatteryId(id: Int): Flow<List<Charge>>
} }

View File

@@ -37,6 +37,10 @@ fun BatteryTracker() {
val state: MainViewModel = viewModel() val state: MainViewModel = viewModel()
state.init() state.init()
/**
* TODO Is there a smarter way to work with Scaffold? Is it possible to change the
* components of the Scaffold from within the composables?
*/
Scaffold( Scaffold(
scaffoldState = state.scaffoldState, scaffoldState = state.scaffoldState,
@@ -67,20 +71,6 @@ fun BatteryTracker() {
route = Routes.HOME, route = Routes.HOME,
state = state, state = state,
) )
NavListItem(
icon = Icons.Default.BatteryChargingFull,
textId = R.string.nav_add_charge,
route = Routes.ADD_CHARGE,
state = state,
)
NavListItem(
icon = Icons.Default.BatteryFull,
textId = R.string.nav_add_battery,
route = Routes.ADD_BATTERY,
state = state,
)
} }
) { ) {
NavHost( NavHost(
@@ -92,9 +82,23 @@ fun BatteryTracker() {
state.currentScreen = Routes.HOME state.currentScreen = Routes.HOME
Home(state.navController) Home(state.navController)
} }
composable(Routes.ADD_CHARGE) { composable(
state.currentScreen = Routes.ADD_CHARGE route = Routes.ADD_CHARGE
AddCharge(state.navController) ){
AddCharge(
navController = state.navController
)
}
composable(
route = "${Routes.ADD_CHARGE}/{batteryId}",
arguments = listOf(navArgument("batteryId"){ type = NavType.IntType })
) {
val id = it.arguments?.getInt("batteryId")
state.currentScreen = "${Routes.ADD_CHARGE} for Battery ${state.getBatteryName(id = id)}"
AddCharge(
batteryId = id,
navController = state.navController
)
} }
composable(Routes.ADD_BATTERY) { composable(Routes.ADD_BATTERY) {
state.currentScreen = Routes.ADD_BATTERY state.currentScreen = Routes.ADD_BATTERY
@@ -107,7 +111,8 @@ fun BatteryTracker() {
val id = navBackStackEntry.arguments?.getInt("batteryId") val id = navBackStackEntry.arguments?.getInt("batteryId")
state.currentScreen = Routes.BATTERY_DETAILS + ": " + state.getBatteryName(id = id) state.currentScreen = Routes.BATTERY_DETAILS + ": " + state.getBatteryName(id = id)
BatteryDetails( BatteryDetails(
batteryId = id, batteryId = id ,
navController = state.navController
) )
} }
} }

View File

@@ -35,7 +35,10 @@ import java.util.*
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun AddCharge(navController: NavController){ fun AddCharge(
batteryId: Int? = null,
navController: NavController
){
val outerPadding = 16.dp val outerPadding = 16.dp
val innerPadding = 16.dp val innerPadding = 16.dp
@@ -43,6 +46,12 @@ fun AddCharge(navController: NavController){
val model: AddChargeViewModel = viewModel() val model: AddChargeViewModel = viewModel()
val batteries by model.batteries.observeAsState(emptyList()) val batteries by model.batteries.observeAsState(emptyList())
if(batteryId != null && batteries.any { it.id == batteryId }) {
model.batteryName = TextFieldValue(
text = batteries.find { it.id == batteryId }!!.name
)
}
Column( Column(
Modifier.padding(outerPadding) Modifier.padding(outerPadding)
) { ) {
@@ -51,11 +60,12 @@ fun AddCharge(navController: NavController){
onExpandedChange = { bIdExpanded = !bIdExpanded} onExpandedChange = { bIdExpanded = !bIdExpanded}
) { ) {
val filteringOptions = batteries.filter { it.name.contains(model.batteryId.text, ignoreCase = true)} val filteringOptions = batteries.filter { it.name.contains(model.batteryName.text, ignoreCase = true)}
OutlinedTextField( OutlinedTextField(
value = model.batteryId, enabled = batteryId == null,
value = model.batteryName,
onValueChange = { onValueChange = {
model.batteryId = it model.batteryName = it
model.batteryHasError = false model.batteryHasError = false
model.batteryHelper = R.string.helper_required model.batteryHelper = R.string.helper_required
bIdExpanded = filteringOptions.size > 1 bIdExpanded = filteringOptions.size > 1
@@ -82,7 +92,7 @@ fun AddCharge(navController: NavController){
for (filteringOption in filteringOptions) { for (filteringOption in filteringOptions) {
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
model.batteryId = TextFieldValue( model.batteryName = TextFieldValue(
text = filteringOption.name, text = filteringOption.name,
selection = TextRange(filteringOption.name.length) selection = TextRange(filteringOption.name.length)
) )
@@ -134,11 +144,11 @@ fun AddCharge(navController: NavController){
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.button_save_charge)) }, text = { Text(stringResource(R.string.button_save_charge)) },
onClick = { onClick = {
if(!batteries.any{ it.name == model.batteryId.text }){ if(!batteries.any{ it.name == model.batteryName.text }){
model.batteryHasError = true model.batteryHasError = true
model.batteryHelper = R.string.helper_battery_not_found model.batteryHelper = R.string.helper_battery_not_found
} }
if(model.batteryId.text.isBlank()){ if(model.batteryName.text.isBlank()){
model.batteryHasError = true model.batteryHasError = true
model.batteryHelper = R.string.helper_required model.batteryHelper = R.string.helper_required
} }
@@ -146,8 +156,12 @@ fun AddCharge(navController: NavController){
model.chargeHasError = true model.chargeHasError = true
} }
if(!model.batteryHasError && !model.chargeHasError && model.saveCharge(batteries, model.batteryId.text, model.charge, model.date)){ if(!model.batteryHasError && !model.chargeHasError && model.saveCharge(batteries, model.batteryName.text, model.charge, model.date)){
navController.navigate(Routes.HOME) val id = model.getBatId(
batteries,
model.batteryName.text
)
navController.navigate("${Routes.BATTERY_DETAILS}/${id}")
} }
}, },
icon = { Icon(Icons.Default.Save, "Icon Save") }, icon = { Icon(Icons.Default.Save, "Icon Save") },

View File

@@ -1,19 +1,21 @@
package com.sockenklaus.batterytracker.ui.composables package com.sockenklaus.batterytracker.ui.composables
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.entities.Battery import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.room.entities.Charge import com.sockenklaus.batterytracker.room.entities.Charge
import com.sockenklaus.batterytracker.ui.AppBarTitle import com.sockenklaus.batterytracker.ui.AppBarTitle
@@ -26,14 +28,16 @@ import java.util.*
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun BatteryDetails( fun BatteryDetails(
batteryId: Int? = null batteryId: Int?,
navController: NavController
){ ){
val model: BatteryDetailsViewModel = viewModel() val model: BatteryDetailsViewModel = viewModel()
val battery by model.battery.collectAsState(Battery(name = "")) val battery by model.battery.collectAsState(Battery(name = ""))
val charges: List<Charge> by model.charges.collectAsState(emptyList()) val charges: List<Charge> by model.charges.collectAsState(emptyList())
Column( Box(
modifier = Modifier.padding(bottom = 16.dp) modifier = Modifier
.fillMaxSize()
){ ){
val outputFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.GERMANY) val outputFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.GERMANY)
@@ -50,8 +54,17 @@ fun BatteryDetails(
Divider(Modifier.padding(horizontal = 16.dp)) Divider(Modifier.padding(horizontal = 16.dp))
} }
} }
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.add_charge)) },
onClick = {
navController.navigate("${Routes.ADD_CHARGE}/${battery.id}")
},
icon = { Icon(Icons.Default.Add, "Icon Add") },
modifier = Modifier.align(Alignment.BottomEnd)
.padding(16.dp)
)
} }
} }
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@@ -76,6 +89,6 @@ fun DetailsTopAppBar(
) { ) {
Icon(Icons.Default.ArrowBack, null) Icon(Icons.Default.ArrowBack, null)
} }
} },
) )
} }

View File

@@ -1,6 +1,5 @@
package com.sockenklaus.batterytracker.ui.composables package com.sockenklaus.batterytracker.ui.composables
import android.inputmethodservice.Keyboard
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -8,9 +7,13 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -33,47 +36,63 @@ fun Home(
val filteredList = batteries.filter { it.name.contains(filterText, ignoreCase = true) } val filteredList = batteries.filter { it.name.contains(filterText, ignoreCase = true) }
Column( Box(
modifier = Modifier.padding(bottom = 16.dp) modifier = Modifier.fillMaxSize()
) { ){
MyOutlinedTextFieldWithSuffix( Column(
value = filterText, modifier = Modifier
onValueChange = { filterText = it }, .padding(bottom = 16.dp)
labelId = R.string.hint_filter_batteries, .fillMaxHeight()
modifier = Modifier.padding(
top = 16.dp,
start = 16.dp,
end = 16.dp
)
.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search
)
)
LazyColumn(
state = LazyListState(),
) { ) {
items(filteredList){ battery -> MyOutlinedTextFieldWithSuffix(
ListItem( value = filterText,
text = { onValueChange = { filterText = it },
ListPrimaryText(battery) labelId = R.string.hint_filter_batteries,
}, modifier = Modifier
secondaryText = { .padding(
ListSecondaryText( top = 16.dp,
min = model.getMinChargeById(battery.id), start = 16.dp,
avg = model.getAvgChargeById(battery.id), end = 16.dp
max = model.getMaxChargeById(battery.id) )
) .fillMaxWidth(),
}, singleLine = true,
modifier = Modifier.clickable { keyboardOptions = KeyboardOptions(
navController.navigate("${Routes.BATTERY_DETAILS}/${battery.id}") imeAction = ImeAction.Search
}
) )
Divider(Modifier.padding(horizontal = 16.dp)) )
LazyColumn(
state = LazyListState(),
) {
items(filteredList){ battery ->
ListItem(
text = {
ListPrimaryText(battery)
},
secondaryText = {
ListSecondaryText(
min = model.getMinChargeById(battery.id),
avg = model.getAvgChargeById(battery.id),
max = model.getMaxChargeById(battery.id)
)
},
modifier = Modifier.clickable {
navController.navigate("${Routes.BATTERY_DETAILS}/${battery.id}")
}
)
Divider(Modifier.padding(horizontal = 16.dp))
}
} }
} }
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.nav_add_battery)) },
icon = { Icon(Icons.Default.Add, contentDescription = "Add Battery") },
onClick = {
navController.navigate(Routes.ADD_BATTERY)
},
modifier = Modifier.padding(16.dp)
.align(Alignment.BottomEnd)
)
} }
} }

View File

@@ -1,7 +1,6 @@
package com.sockenklaus.batterytracker.ui.models package com.sockenklaus.batterytracker.ui.models
import android.app.Application import android.app.Application
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -23,7 +22,7 @@ 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 by mutableStateOf(TextFieldValue("")) var batteryName by mutableStateOf(TextFieldValue(""))
var date: LocalDate by mutableStateOf(LocalDate.now()) var date: LocalDate by mutableStateOf(LocalDate.now())
var charge by mutableStateOf("") var charge by mutableStateOf("")
@@ -32,6 +31,14 @@ class AddChargeViewModel(application: Application) : AndroidViewModel(applicatio
var chargeHasError by mutableStateOf(false) var chargeHasError by mutableStateOf(false)
fun getBatId(
batteries: List<Battery>,
name: String
) : Int? {
return batteries.find { it.name == name }?.id
}
fun saveCharge( fun saveCharge(
batteryList: List<Battery>, batteryList: List<Battery>,
batteryName: String, batteryName: String,

View File

@@ -7,9 +7,6 @@ import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.room.entities.Charge import com.sockenklaus.batterytracker.room.entities.Charge
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
class BatteryDetailsViewModel( class BatteryDetailsViewModel(
application: Application, application: Application,

View File

@@ -11,7 +11,7 @@
<string name="nav_header_title">Navigation</string> <string name="nav_header_title">Navigation</string>
<string name="nav_home">Home</string> <string name="nav_home">Home</string>
<string name="nav_add_charge">Add Charge</string> <string name="add_charge">Add Charge</string>
<string name="nav_add_battery">Add Battery</string> <string name="nav_add_battery">Add Battery</string>
<string name="battery_id">Battery ID</string> <string name="battery_id">Battery ID</string>