Implemented first BatteryDetails.kt Composable and made TopAppBar dynamic.

This commit is contained in:
sockenklaus
2022-07-22 02:51:02 +02:00
parent f172fa4f89
commit 69f12eb78f
5 changed files with 143 additions and 51 deletions

View File

@@ -14,15 +14,22 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.sockenklaus.batterytracker.R import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.ui.composables.AddBattery import com.sockenklaus.batterytracker.ui.composables.*
import com.sockenklaus.batterytracker.ui.composables.AddCharge
import com.sockenklaus.batterytracker.ui.composables.Home
import com.sockenklaus.batterytracker.ui.models.MainViewModel import com.sockenklaus.batterytracker.ui.models.MainViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object Routes {
const val HOME = "home"
const val ADD_BATTERY = "add_battery"
const val ADD_CHARGE = "add_charge"
const val BATTERY_DETAILS = "battery_details"
}
@Composable @Composable
fun BatteryTracker() { fun BatteryTracker() {
MaterialTheme { MaterialTheme {
@@ -33,17 +40,19 @@ fun BatteryTracker() {
scaffoldState = state.scaffoldState, scaffoldState = state.scaffoldState,
topBar = { topBar = {
if(state.showAppBar){ when(state.currentScreen){
TopAppBar (
title = { AppBarTitle(state = state) }, Routes.BATTERY_DETAILS -> DetailsTopAppBar(
navigationIcon = { navController = state.navController,
ToggleDrawerButton( changeCurrentScreen = { state.currentScreen = it }
state = state, )
)
} else -> MainTopAppBar(
drawerState = state.scaffoldState.drawerState,
appTitle = state.appTitle
) )
} }
}, }
) { innerPadding -> ) { innerPadding ->
ModalDrawer( ModalDrawer(
drawerState = state.scaffoldState.drawerState, drawerState = state.scaffoldState.drawerState,
@@ -51,46 +60,57 @@ fun BatteryTracker() {
NavListItem( NavListItem(
textId = R.string.nav_home, textId = R.string.nav_home,
icon = Icons.Default.Home, icon = Icons.Default.Home,
route = "home", route = Routes.HOME,
state = state, state = state,
) )
NavListItem( NavListItem(
icon = Icons.Default.BatteryChargingFull, icon = Icons.Default.BatteryChargingFull,
textId = R.string.nav_add_charge, textId = R.string.nav_add_charge,
route = "add_charge", route = Routes.ADD_CHARGE,
state = state, state = state,
) )
NavListItem( NavListItem(
icon = Icons.Default.BatteryFull, icon = Icons.Default.BatteryFull,
textId = R.string.nav_add_battery, textId = R.string.nav_add_battery,
route = "add_battery", route = Routes.ADD_BATTERY,
state = state, state = state,
) )
} }
) { ) {
NavHost( NavHost(
navController = state.navController, navController = state.navController,
startDestination = "home", startDestination = Routes.HOME,
modifier = Modifier.padding(innerPadding)) modifier = Modifier.padding(innerPadding))
{ {
composable("home") { Home(state.navController) } composable(Routes.HOME) { Home(state.navController, state) }
composable("add_charge") { AddCharge(state.navController) } composable(Routes.ADD_CHARGE) { AddCharge(state.navController) }
composable("add_battery") { AddBattery(state.navController) } composable(Routes.ADD_BATTERY) { AddBattery(state.navController) }
composable(
route = "${Routes.BATTERY_DETAILS}/{batteryId}",
arguments = listOf(navArgument("batteryId"){ type = NavType.IntType })
) { navBackStackEntry ->
BatteryDetails(
navController = state.navController,
batteryId = navBackStackEntry.arguments?.getInt("batteryId"),
appState = state
)
}
} }
} }
} }
} }
} }
@OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
fun AppBarTitle( fun AppBarTitle(
state: MainViewModel drawerTarget: DrawerValue,
appTitle: String,
){ ){
val text = if(state.scaffoldState.drawerState.targetValue == DrawerValue.Closed) { val text = if(drawerTarget == DrawerValue.Closed) {
state.appTitle appTitle
} else { } else {
stringResource(R.string.nav_header_title) stringResource(R.string.nav_header_title)
} }
@@ -103,11 +123,11 @@ fun AppBarTitle(
@Composable @Composable
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
fun ToggleDrawerButton( fun ToggleDrawerButton(
state: MainViewModel drawerState: DrawerState
){ ){
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val icon = if(state.scaffoldState.drawerState.targetValue == DrawerValue.Open) { val icon = if(drawerState.targetValue == DrawerValue.Open) {
Icons.Default.Close Icons.Default.Close
} else { } else {
Icons.Default.Menu Icons.Default.Menu
@@ -115,8 +135,8 @@ fun ToggleDrawerButton(
IconButton( IconButton(
onClick = { onClick = {
if(state.scaffoldState.drawerState.isClosed) scope.launch { state.scaffoldState.drawerState.open() } if(drawerState.isClosed) scope.launch { drawerState.open() }
else scope.launch { state.scaffoldState.drawerState.close() } else scope.launch { drawerState.close() }
} }
) { ) {
AnimatedContent(targetState = icon) { targetState -> AnimatedContent(targetState = icon) { targetState ->
@@ -134,17 +154,39 @@ fun NavListItem(
state: MainViewModel state: MainViewModel
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val text = if ( textId != null) stringResource(textId) else "" val text = if ( null != textId) stringResource(textId) else ""
ListItem( ListItem(
icon = { Icon(icon, null) }, icon = { Icon(icon, null) },
text = { Text(text) }, text = { Text(text) },
modifier = Modifier.clickable { modifier = Modifier
scope.launch { .clickable {
state.appTitle = text scope.launch {
state.navController.navigate(route) state.appTitle = text
state.scaffoldState.drawerState.close() state.navController.navigate(route)
state.currentScreen = route
state.scaffoldState.drawerState.close()
}
} }
}.wrapContentWidth() .wrapContentWidth()
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MainTopAppBar(
drawerState: DrawerState,
appTitle: String
){
TopAppBar (
title = { AppBarTitle(
drawerTarget = drawerState.targetValue,
appTitle = appTitle
) },
navigationIcon = {
ToggleDrawerButton(
drawerState = drawerState,
)
}
) )
} }

View File

@@ -1,8 +1,42 @@
package com.sockenklaus.batterytracker.ui.composables package com.sockenklaus.batterytracker.ui.composables
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.sockenklaus.batterytracker.ui.Routes
import com.sockenklaus.batterytracker.ui.models.MainViewModel
@Composable @Composable
fun BatteryDetails(){ fun BatteryDetails(
navController: NavController,
batteryId: Int? = null,
appState: MainViewModel
){
Text(batteryId.toString())
} }
@Composable
fun DetailsTopAppBar(
navController: NavController,
changeCurrentScreen: (String) -> Unit
){
TopAppBar(
title = { Text("Details") },
navigationIcon = {
IconButton(
onClick = {
navController.navigate("home")
changeCurrentScreen(Routes.HOME)
}
) {
Icon(Icons.Default.ArrowBack, null)
}
}
)
}

View File

@@ -1,5 +1,7 @@
package com.sockenklaus.batterytracker.ui.composables package com.sockenklaus.batterytracker.ui.composables
import android.telecom.Call
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
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
@@ -15,13 +17,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.sockenklaus.batterytracker.R import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.entities.Battery import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.Routes
import com.sockenklaus.batterytracker.ui.composables.util.MyOutlinedTextFieldWithSuffix import com.sockenklaus.batterytracker.ui.composables.util.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.models.HomeViewModel import com.sockenklaus.batterytracker.ui.models.HomeViewModel
import com.sockenklaus.batterytracker.ui.models.MainViewModel
import java.lang.reflect.GenericDeclaration import java.lang.reflect.GenericDeclaration
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun Home(navController: NavController) { fun Home(
navController: NavController,
appState: MainViewModel
) {
val model: HomeViewModel = viewModel() val model: HomeViewModel = viewModel()
val batteries by model.batteries.observeAsState(emptyList<Battery>()) val batteries by model.batteries.observeAsState(emptyList<Battery>())
var filterText by remember { mutableStateOf("")} var filterText by remember { mutableStateOf("")}
@@ -55,6 +62,10 @@ fun Home(navController: NavController) {
avg = model.getAvgChargeById(battery.id), avg = model.getAvgChargeById(battery.id),
max = model.getMaxChargeById(battery.id) max = model.getMaxChargeById(battery.id)
) )
},
modifier = Modifier.clickable {
navController.navigate("${Routes.BATTERY_DETAILS}/${battery.id}")
appState.currentScreen = Routes.BATTERY_DETAILS
} }
) )
Divider(modHorizontalPadding) Divider(modHorizontalPadding)
@@ -78,7 +89,7 @@ fun ListPrimaryText(
if(battery.declaredCapacity != null){ if(battery.declaredCapacity != null){
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text( Text(
text = "Cap: ${battery.declaredCapacity} Ah", text = "Cap: ${"%.2f".format(battery.declaredCapacity)} Ah",
style = MaterialTheme.typography.body2, style = MaterialTheme.typography.body2,
modifier = Modifier.alignByBaseline() modifier = Modifier.alignByBaseline()
) )
@@ -98,8 +109,8 @@ fun ListSecondaryText(
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text(if(min != null) "Min.: $min Ah" else "") Text(if(min != null) "Min.: ${"%.2f".format(min)} Ah" else "")
Text(if(avg != null) "Avg.: $avg Ah" else "") Text(if(avg != null) "Avg.: ${"%.2f".format(avg)} Ah" else "")
Text(if(max != null) "Max.: $max Ah" else "") Text(if(max != null) "Max.: ${"%.2f".format(max)} Ah" else "")
} }
} }

View File

@@ -13,14 +13,14 @@ class HomeViewModel(application: Application) : AndroidViewModel(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()
private val batteriesAndCharges = db.batteryDao().getBatteriesAndCharges().asLiveData() private val _batteriesAndCharges = db.batteryDao().getBatteriesAndCharges().asLiveData()
@Composable @Composable
fun getMinChargeById(batteryId: Int): Double? { fun getMinChargeById(batteryId: Int): Double? {
val _batteriesAndCharges by batteriesAndCharges.observeAsState() val batteriesAndCharges by _batteriesAndCharges.observeAsState()
val batteryAndCharges = _batteriesAndCharges?.find{ it.battery.id == batteryId } val batteryAndCharges = batteriesAndCharges?.find{ it.battery.id == batteryId }
if(batteryAndCharges != null && batteryAndCharges.charges.isNotEmpty()){ if((batteryAndCharges != null) && batteryAndCharges.charges.isNotEmpty()){
return batteryAndCharges.charges.map { it.charge }.min() return batteryAndCharges.charges.map { it.charge }.min()
} }
return null return null
@@ -28,10 +28,10 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
@Composable @Composable
fun getMaxChargeById(batteryId: Int): Double? { fun getMaxChargeById(batteryId: Int): Double? {
val _batteriesAndCharges by batteriesAndCharges.observeAsState() val batteriesAndCharges by _batteriesAndCharges.observeAsState()
val batteryAndCharges = _batteriesAndCharges?.find { it.battery.id == batteryId } val batteryAndCharges = batteriesAndCharges?.find { it.battery.id == batteryId }
if(batteryAndCharges != null && batteryAndCharges.charges.isNotEmpty()) { if((batteryAndCharges != null) && batteryAndCharges.charges.isNotEmpty()) {
return batteryAndCharges.charges.map { it.charge }.max() return batteryAndCharges.charges.map { it.charge }.max()
} }
return null return null
@@ -39,10 +39,10 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
@Composable @Composable
fun getAvgChargeById(batteryId: Int): Double? { fun getAvgChargeById(batteryId: Int): Double? {
val _batteriesAndCharges by batteriesAndCharges.observeAsState() val batteriesAndCharges by _batteriesAndCharges.observeAsState()
val batteryAndCharges = _batteriesAndCharges?.find{ it.battery.id == batteryId } val batteryAndCharges = batteriesAndCharges?.find{ it.battery.id == batteryId }
if(batteryAndCharges != null && batteryAndCharges.charges.isNotEmpty()) { if((batteryAndCharges != null) && batteryAndCharges.charges.isNotEmpty()) {
return BigDecimal(batteryAndCharges.charges.map { it.charge }.average()).setScale(2, RoundingMode.HALF_UP).toDouble() return BigDecimal(batteryAndCharges.charges.map { it.charge }.average()).setScale(2, RoundingMode.HALF_UP).toDouble()
} }
return null return null

View File

@@ -2,14 +2,19 @@ package com.sockenklaus.batterytracker.ui.models
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.graphics.BlendMode.Companion.Screen
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.sockenklaus.batterytracker.ui.AppBarTitle
import com.sockenklaus.batterytracker.ui.Routes
import com.sockenklaus.batterytracker.ui.ToggleDrawerButton
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
var showAppBar by mutableStateOf(true)
var appTitle by mutableStateOf("Home") var appTitle by mutableStateOf("Home")
var currentScreen by mutableStateOf(Routes.HOME)
lateinit var navController: NavHostController lateinit var navController: NavHostController
lateinit var scaffoldState: ScaffoldState lateinit var scaffoldState: ScaffoldState