Compare commits
10 Commits
5ea0a4a921
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84c40317a5 | ||
|
|
cd1ce2ccda | ||
|
|
a7c92b020b | ||
|
|
c20d5d55e5 | ||
|
|
49eabce142 | ||
|
|
6e0eb1c8f4 | ||
|
|
0f99fb589c | ||
|
|
8ca5db7c52 | ||
|
|
3b2e09c134 | ||
|
|
ebbee65330 |
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<targetSelectedWithDropDown>
|
||||||
|
<Target>
|
||||||
|
<type value="QUICK_BOOT_TARGET" />
|
||||||
|
<deviceKey>
|
||||||
|
<Key>
|
||||||
|
<type value="VIRTUAL_DEVICE_PATH" />
|
||||||
|
<value value="C:\Android\.android\avd\Pixel_3a_API_30_x86.avd" />
|
||||||
|
</Key>
|
||||||
|
</deviceKey>
|
||||||
|
</Target>
|
||||||
|
</targetSelectedWithDropDown>
|
||||||
|
<timeTargetWasSelectedWithDropDown value="2022-11-16T20:26:11.531085900Z" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -11,12 +11,12 @@ android {
|
|||||||
release {
|
release {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileSdk 32
|
compileSdk 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.sockenklaus.batterytracker"
|
applicationId "com.sockenklaus.batterytracker"
|
||||||
minSdk 29
|
minSdk 29
|
||||||
targetSdk 32
|
targetSdk 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -63,50 +65,46 @@ android {
|
|||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace 'com.sockenklaus.batterytracker'
|
||||||
|
flavorDimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.room:room-runtime:2.4.2'
|
implementation 'androidx.room:room-runtime:2.4.3'
|
||||||
implementation 'androidx.room:room-rxjava3:2.4.2'
|
implementation 'androidx.room:room-rxjava3:2.4.3'
|
||||||
implementation "androidx.room:room-paging:2.4.2"
|
implementation "androidx.room:room-paging:2.4.3"
|
||||||
implementation "androidx.room:room-ktx:2.4.2"
|
implementation "androidx.room:room-ktx:2.4.3"
|
||||||
kapt 'androidx.room:room-compiler:2.4.2'
|
kapt 'androidx.room:room-compiler:2.4.3'
|
||||||
|
|
||||||
implementation "androidx.compose.ui:ui:1.3.0-alpha01"
|
implementation "androidx.compose.ui:ui:1.4.0-alpha02"
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview:1.3.0-alpha01"
|
implementation "androidx.compose.ui:ui-tooling-preview:1.4.0-alpha02"
|
||||||
implementation 'androidx.compose.material:material-icons-extended:1.3.0-alpha01'
|
implementation 'androidx.compose.material:material-icons-extended:1.4.0-alpha02'
|
||||||
implementation 'androidx.compose.material:material:1.3.0-alpha01'
|
implementation 'androidx.compose.material:material:1.4.0-alpha02'
|
||||||
implementation 'androidx.compose.runtime:runtime-livedata:1.1.1'
|
implementation 'androidx.compose.runtime:runtime-livedata:1.3.1'
|
||||||
implementation 'androidx.compose.animation:animation:1.3.0-alpha01'
|
implementation 'androidx.compose.animation:animation:1.4.0-alpha02'
|
||||||
implementation 'androidx.compose.ui:ui-tooling:1.3.0-alpha01'
|
implementation 'androidx.compose.ui:ui-tooling:1.4.0-alpha02'
|
||||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.3.0-alpha01'
|
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.4.0-alpha02'
|
||||||
debugImplementation "androidx.compose.ui:ui-test-manifest:1.1.1"
|
debugImplementation "androidx.compose.ui:ui-test-manifest:1.3.1"
|
||||||
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
|
||||||
|
|
||||||
|
implementation 'androidx.core:core-ktx:1.9.0'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||||
implementation 'com.google.android.material:material:1.7.0-alpha03'
|
|
||||||
implementation 'com.google.android.material:compose-theme-adapter:1.1.14'
|
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.8.0'
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
|
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
|
||||||
|
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
|
||||||
implementation "androidx.navigation:navigation-compose:2.5.0"
|
implementation "androidx.navigation:navigation-compose:2.5.3"
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
implementation 'androidx.activity:activity-compose:1.5.0'
|
implementation 'androidx.activity:activity-compose:1.6.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
package="com.sockenklaus.batterytracker">
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ interface BatteryDao {
|
|||||||
@Update
|
@Update
|
||||||
fun updateBattery(battery: Battery): Int
|
fun updateBattery(battery: Battery): Int
|
||||||
|
|
||||||
@Query("Select * FROM batteries ORDER BY name ASC")
|
@Query("Select * FROM batteries ORDER BY name COLLATE NOCASE ASC")
|
||||||
fun getBatteries(): Flow<List<Battery>>
|
fun getBatteries(): Flow<List<Battery>>
|
||||||
|
|
||||||
@Query("Select * FROM batteries WHERE id = :id")
|
@Query("SELECT * FROM batteries WHERE id = :id")
|
||||||
fun getBatteryById(id: Int): Flow<Battery>
|
fun getBatteryById(id: Int): Flow<Battery>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
|
|||||||
@@ -25,4 +25,7 @@ interface ChargeDao {
|
|||||||
@Transaction
|
@Transaction
|
||||||
@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 created_at DESC")
|
||||||
|
fun getChargesByBatteryId(id: Int): Flow<List<Charge>>
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.sockenklaus.batterytracker.ui
|
package com.sockenklaus.batterytracker.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -24,36 +24,44 @@ import com.sockenklaus.batterytracker.ui.models.MainViewModel
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
object Routes {
|
object Routes {
|
||||||
const val HOME = "home"
|
const val HOME = "Home"
|
||||||
const val ADD_BATTERY = "add_battery"
|
const val ADD_BATTERY = "Add Battery"
|
||||||
const val ADD_CHARGE = "add_charge"
|
const val ADD_CHARGE = "Add Charge"
|
||||||
const val BATTERY_DETAILS = "battery_details"
|
const val BATTERY_DETAILS = "Battery Details"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
|
||||||
@Composable
|
@Composable
|
||||||
fun BatteryTracker() {
|
fun BatteryTracker() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
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,
|
||||||
|
|
||||||
topBar = {
|
topBar = {
|
||||||
when(state.currentScreen){
|
when {
|
||||||
|
state.currentScreen.startsWith(Routes.BATTERY_DETAILS) -> {
|
||||||
|
|
||||||
Routes.BATTERY_DETAILS -> DetailsTopAppBar(
|
DetailsTopAppBar(
|
||||||
navController = state.navController,
|
navController = state.navController,
|
||||||
changeCurrentScreen = { state.currentScreen = it }
|
drawerState = state.scaffoldState.drawerState,
|
||||||
|
appTitle = state.currentScreen
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
else -> MainTopAppBar(
|
else -> MainTopAppBar(
|
||||||
drawerState = state.scaffoldState.drawerState,
|
drawerState = state.scaffoldState.drawerState,
|
||||||
appTitle = state.appTitle
|
appTitle = state.currentScreen,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) {
|
||||||
ModalDrawer(
|
ModalDrawer(
|
||||||
drawerState = state.scaffoldState.drawerState,
|
drawerState = state.scaffoldState.drawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
@@ -63,38 +71,48 @@ 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(
|
||||||
navController = state.navController,
|
navController = state.navController,
|
||||||
startDestination = Routes.HOME,
|
startDestination = Routes.HOME,
|
||||||
modifier = Modifier.padding(innerPadding))
|
)
|
||||||
{
|
{
|
||||||
composable(Routes.HOME) { Home(state.navController, state) }
|
composable(Routes.HOME) {
|
||||||
composable(Routes.ADD_CHARGE) { AddCharge(state.navController) }
|
state.currentScreen = Routes.HOME
|
||||||
composable(Routes.ADD_BATTERY) { AddBattery(state.navController) }
|
Home(state.navController)
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
route = Routes.ADD_CHARGE
|
||||||
|
){
|
||||||
|
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) {
|
||||||
|
state.currentScreen = Routes.ADD_BATTERY
|
||||||
|
AddBattery(state.navController)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = "${Routes.BATTERY_DETAILS}/{batteryId}",
|
route = "${Routes.BATTERY_DETAILS}/{batteryId}",
|
||||||
arguments = listOf(navArgument("batteryId"){ type = NavType.IntType })
|
arguments = listOf(navArgument("batteryId"){ type = NavType.IntType })
|
||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
|
val id = navBackStackEntry.arguments?.getInt("batteryId")
|
||||||
|
state.currentScreen = Routes.BATTERY_DETAILS + ": " + state.getBatteryName(id = id)
|
||||||
BatteryDetails(
|
BatteryDetails(
|
||||||
navController = state.navController,
|
batteryId = id ,
|
||||||
batteryId = navBackStackEntry.arguments?.getInt("batteryId"),
|
navController = state.navController
|
||||||
appState = state
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,9 +180,7 @@ fun NavListItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
state.appTitle = text
|
|
||||||
state.navController.navigate(route)
|
state.navController.navigate(route)
|
||||||
state.currentScreen = route
|
|
||||||
state.scaffoldState.drawerState.close()
|
state.scaffoldState.drawerState.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sockenklaus.batterytracker.ui.composables
|
package com.sockenklaus.batterytracker.ui.composables
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
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.Icons
|
||||||
@@ -10,11 +11,14 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||||||
import androidx.compose.ui.Alignment
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
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.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.R
|
||||||
|
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.AddBatteryViewModel
|
import com.sockenklaus.batterytracker.ui.models.AddBatteryViewModel
|
||||||
import com.sockenklaus.batterytracker.util.validateDecimal
|
import com.sockenklaus.batterytracker.util.validateDecimal
|
||||||
@@ -24,13 +28,18 @@ fun AddBattery(navController: NavController){
|
|||||||
val model: AddBatteryViewModel = viewModel()
|
val model: AddBatteryViewModel = viewModel()
|
||||||
val batteries by model.batteries.observeAsState(emptyList())
|
val batteries by model.batteries.observeAsState(emptyList())
|
||||||
|
|
||||||
val outerPadding = 24.dp
|
val outerPadding = 16.dp
|
||||||
val innerPadding = 16.dp
|
val innerPadding = 16.dp
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier.padding(outerPadding)
|
Modifier.padding(outerPadding)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
){
|
||||||
|
|
||||||
MyOutlinedTextFieldWithSuffix(
|
MyOutlinedTextFieldWithSuffix(
|
||||||
value = model.batteryName,
|
value = model.batteryName,
|
||||||
onValueChange = { value ->
|
onValueChange = { value ->
|
||||||
@@ -42,11 +51,32 @@ fun AddBattery(navController: NavController){
|
|||||||
model.batteryHelperId = R.string.helper_battery_not_unique
|
model.batteryHelperId = R.string.helper_battery_not_unique
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
singleLine = true,
|
||||||
labelId = R.string.hint_enter_battery_name,
|
labelId = R.string.hint_enter_battery_name,
|
||||||
leadingIcon = { Icon(Icons.Default.Tag, "Icon Tag") },
|
leadingIcon = { Icon(Icons.Default.Tag, "Icon Tag") },
|
||||||
isError = model.batteryHasError,
|
isError = model.batteryHasError,
|
||||||
helperTextId = model.batteryHelperId
|
helperTextId = model.batteryHelperId,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
capitalization = if(model.switchAutoCap) KeyboardCapitalization.Characters else KeyboardCapitalization.None,
|
||||||
|
autoCorrect = false,
|
||||||
|
keyboardType = KeyboardType.Ascii,
|
||||||
|
imeAction = ImeAction.Next
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
Switch(
|
||||||
|
checked = model.switchAutoCap,
|
||||||
|
onCheckedChange = { model.switchAutoCap = !model.switchAutoCap }
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Auto-Cap.",
|
||||||
|
style = MaterialTheme.typography.caption
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Spacer(Modifier.size(innerPadding))
|
Spacer(Modifier.size(innerPadding))
|
||||||
|
|
||||||
@@ -56,24 +86,38 @@ fun AddBattery(navController: NavController){
|
|||||||
model.declaredCapacity = validateDecimal(it, model.declaredCapacity)
|
model.declaredCapacity = validateDecimal(it, model.declaredCapacity)
|
||||||
},
|
},
|
||||||
labelId = R.string.hint_enter_declared_capacity,
|
labelId = R.string.hint_enter_declared_capacity,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Decimal,
|
||||||
|
imeAction = ImeAction.Done
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
saveBattery(model = model, navController = navController)
|
||||||
|
}
|
||||||
|
),
|
||||||
leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") },
|
leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") },
|
||||||
suffix = "Ah"
|
suffix = "Ah",
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.size(outerPadding))
|
Spacer(Modifier.size(outerPadding))
|
||||||
|
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
onClick = {
|
onClick = { saveBattery(model = model, navController = navController) },
|
||||||
if(model.batteryName.isBlank()) model.batteryHasError = true
|
|
||||||
|
|
||||||
if(!model.batteryHasError && model.saveBattery(model.batteryName, model.declaredCapacity)){
|
|
||||||
navController.navigate("home")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = { Icon(Icons.Default.Save, "Icon Save") },
|
icon = { Icon(Icons.Default.Save, "Icon Save") },
|
||||||
text = { Text(stringResource(R.string.button_save_battery)) },
|
text = { Text(stringResource(R.string.button_save_battery)) },
|
||||||
modifier = Modifier.align(Alignment.End)
|
modifier = Modifier.align(Alignment.End)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveBattery(
|
||||||
|
model: AddBatteryViewModel,
|
||||||
|
navController: NavController
|
||||||
|
){
|
||||||
|
if(model.batteryName.isBlank()) model.batteryHasError = true
|
||||||
|
|
||||||
|
if(!model.batteryHasError && model.saveBattery(model.batteryName, model.declaredCapacity)) {
|
||||||
|
navController.navigate(Routes.HOME)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.sockenklaus.batterytracker.ui.composables
|
|||||||
|
|
||||||
import android.app.DatePickerDialog
|
import android.app.DatePickerDialog
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
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.Icons
|
||||||
@@ -16,12 +17,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
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.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.R
|
||||||
|
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.AddChargeViewModel
|
import com.sockenklaus.batterytracker.ui.models.AddChargeViewModel
|
||||||
import com.sockenklaus.batterytracker.ui.theme.Gray500
|
import com.sockenklaus.batterytracker.ui.theme.Gray500
|
||||||
@@ -33,14 +37,23 @@ import java.util.*
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AddCharge(navController: NavController){
|
fun AddCharge(
|
||||||
val outerPadding = 24.dp
|
batteryId: Int? = null,
|
||||||
|
navController: NavController
|
||||||
|
){
|
||||||
|
val outerPadding = 16.dp
|
||||||
val innerPadding = 16.dp
|
val innerPadding = 16.dp
|
||||||
|
|
||||||
var bIdExpanded by remember { mutableStateOf(false)}
|
var bIdExpanded by remember { mutableStateOf(false)}
|
||||||
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)
|
||||||
) {
|
) {
|
||||||
@@ -49,11 +62,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
|
||||||
@@ -65,7 +79,17 @@ fun AddCharge(navController: NavController){
|
|||||||
},
|
},
|
||||||
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
isError = model.batteryHasError
|
isError = model.batteryHasError,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Next
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onNext = {
|
||||||
|
bIdExpanded = false
|
||||||
|
defaultKeyboardAction(ImeAction.Next)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
singleLine = true
|
||||||
)
|
)
|
||||||
|
|
||||||
if(filteringOptions.isNotEmpty()) {
|
if(filteringOptions.isNotEmpty()) {
|
||||||
@@ -76,7 +100,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)
|
||||||
)
|
)
|
||||||
@@ -104,12 +128,21 @@ fun AddCharge(navController: NavController){
|
|||||||
model.charge = validateDecimal(it, model.charge)
|
model.charge = validateDecimal(it, model.charge)
|
||||||
model.chargeHasError = false
|
model.chargeHasError = false
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Decimal,
|
||||||
|
imeAction = ImeAction.Done
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
saveCharge(batteries, model, navController)
|
||||||
|
}
|
||||||
|
),
|
||||||
labelId = R.string.hint_charge,
|
labelId = R.string.hint_charge,
|
||||||
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Charging Full") },
|
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Charging Full") },
|
||||||
isError = model.chargeHasError,
|
isError = model.chargeHasError,
|
||||||
helperTextId = R.string.helper_required,
|
helperTextId = R.string.helper_required,
|
||||||
suffix = "Ah"
|
suffix = "Ah",
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.size(innerPadding))
|
Spacer(Modifier.size(innerPadding))
|
||||||
@@ -124,21 +157,18 @@ 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
|
||||||
}
|
}
|
||||||
if(model.charge.isBlank()){
|
if(model.charge.isBlank()){
|
||||||
model.chargeHasError = true
|
model.chargeHasError = true
|
||||||
}
|
}
|
||||||
|
saveCharge(batteries, model, navController)
|
||||||
if(!model.batteryHasError && !model.chargeHasError && model.saveCharge(batteries, model.batteryId.text, model.charge, model.date)){
|
|
||||||
navController.navigate("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)
|
||||||
@@ -147,6 +177,31 @@ fun AddCharge(navController: NavController){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveCharge(
|
||||||
|
batteries: List<Battery>,
|
||||||
|
model: AddChargeViewModel,
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
if(!batteries.any{ it.name == model.batteryName.text }){
|
||||||
|
model.batteryHasError = true
|
||||||
|
model.batteryHelper = R.string.helper_battery_not_found
|
||||||
|
}
|
||||||
|
if(model.batteryName.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.batteryName.text, model.charge, model.date)){
|
||||||
|
val id = model.getBatId(
|
||||||
|
batteries,
|
||||||
|
model.batteryName.text
|
||||||
|
)
|
||||||
|
navController.navigate("${Routes.BATTERY_DETAILS}/${id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChargeDatePicker(
|
fun ChargeDatePicker(
|
||||||
|
|||||||
@@ -1,77 +1,94 @@
|
|||||||
package com.sockenklaus.batterytracker.ui.composables
|
package com.sockenklaus.batterytracker.ui.composables
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.activity.OnBackPressedDispatcher
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TopAppBar
|
|
||||||
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.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
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.Charge
|
||||||
|
import com.sockenklaus.batterytracker.ui.AppBarTitle
|
||||||
import com.sockenklaus.batterytracker.ui.Routes
|
import com.sockenklaus.batterytracker.ui.Routes
|
||||||
import com.sockenklaus.batterytracker.ui.models.MainViewModel
|
import com.sockenklaus.batterytracker.ui.models.BatteryDetailsViewModel
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.FormatStyle
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BatteryDetails(
|
fun BatteryDetails(
|
||||||
navController: NavController,
|
batteryId: Int?,
|
||||||
batteryId: Int? = null,
|
navController: NavController
|
||||||
appState: MainViewModel
|
|
||||||
){
|
){
|
||||||
var onBack = {
|
val model: BatteryDetailsViewModel = viewModel()
|
||||||
val backTarget = navController.previousBackStackEntry?.destination?.route ?: Routes.HOME
|
val battery by model.battery.collectAsState(Battery(name = ""))
|
||||||
appState.currentScreen = backTarget
|
val charges: List<Charge> by model.charges.collectAsState(emptyList())
|
||||||
navController.navigate(backTarget)
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
){
|
||||||
|
|
||||||
|
val outputFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.GERMANY)
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
state = LazyListState()
|
||||||
|
) {
|
||||||
|
items(charges){ charge ->
|
||||||
|
ListItem(
|
||||||
|
text = { Text("Charge: " + charge.charge.toString() + " Ah") },
|
||||||
|
secondaryText = { Text("Date: " + charge.date.format(outputFormat)) }
|
||||||
|
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BackPressHandler(onBackPressed = onBack)
|
|
||||||
|
|
||||||
Text(batteryId.toString())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailsTopAppBar(
|
fun DetailsTopAppBar(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
changeCurrentScreen: (String) -> Unit
|
appTitle: String,
|
||||||
|
drawerState: DrawerState
|
||||||
){
|
){
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Details") },
|
title = {
|
||||||
|
AppBarTitle(
|
||||||
|
drawerTarget = drawerState.targetValue,
|
||||||
|
appTitle = appTitle
|
||||||
|
)
|
||||||
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate("home")
|
navController.navigate(Routes.HOME)
|
||||||
changeCurrentScreen(Routes.HOME)
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.ArrowBack, null)
|
Icon(Icons.Default.ArrowBack, null)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BackPressHandler(
|
|
||||||
backPressedDispatcher: OnBackPressedDispatcher? = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher,
|
|
||||||
onBackPressed: () -> Unit
|
|
||||||
) {
|
|
||||||
val currentOnBackPressed by rememberUpdatedState(newValue = onBackPressed)
|
|
||||||
|
|
||||||
val backCallback = remember {
|
|
||||||
object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
currentOnBackPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposableEffect(key1 = backPressedDispatcher) {
|
|
||||||
backPressedDispatcher?.addCallback(backCallback)
|
|
||||||
|
|
||||||
onDispose {
|
|
||||||
backCallback.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
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.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
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
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.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.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
|
||||||
@@ -20,36 +25,50 @@ import com.sockenklaus.batterytracker.room.entities.Battery
|
|||||||
import com.sockenklaus.batterytracker.ui.Routes
|
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
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Home(
|
fun Home(
|
||||||
navController: NavController,
|
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("")}
|
||||||
|
|
||||||
val filteredList = batteries.filter { it.name.contains(filterText, ignoreCase = true) }
|
val filteredList = batteries.filter { it.name.contains(filterText, ignoreCase = true) }
|
||||||
val modHorizontalPadding = Modifier.padding(horizontal = 16.dp)
|
|
||||||
|
|
||||||
Column {
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
){
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
) {
|
||||||
MyOutlinedTextFieldWithSuffix(
|
MyOutlinedTextFieldWithSuffix(
|
||||||
value = filterText,
|
value = filterText,
|
||||||
onValueChange = { filterText = it },
|
onValueChange = { filterText = it },
|
||||||
labelId = R.string.hint_filter_batteries,
|
labelId = R.string.hint_filter_batteries,
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
top = 16.dp,
|
||||||
start = 16.dp,
|
start = 16.dp,
|
||||||
end = 16.dp,
|
end = 16.dp
|
||||||
top = 16.dp
|
)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Search
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = LazyListState()
|
state = LazyListState(),
|
||||||
) {
|
) {
|
||||||
items(filteredList){ battery ->
|
items(filteredList){ battery ->
|
||||||
ListItem(
|
ListItem(
|
||||||
@@ -65,13 +84,22 @@ fun Home(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
navController.navigate("${Routes.BATTERY_DETAILS}/${battery.id}")
|
navController.navigate("${Routes.BATTERY_DETAILS}/${battery.id}")
|
||||||
appState.currentScreen = Routes.BATTERY_DETAILS
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Divider(modHorizontalPadding)
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -21,90 +21,6 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.sockenklaus.batterytracker.ui.theme.Gray500
|
import com.sockenklaus.batterytracker.ui.theme.Gray500
|
||||||
|
|
||||||
/*@Composable
|
|
||||||
fun TopAppBar(
|
|
||||||
title: @Composable () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
navigationIcon: @Composable (() -> Unit)? = null,
|
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
|
||||||
backgroundColor: Color = MaterialTheme.colors.primarySurface,
|
|
||||||
contentColor: Color = contentColorFor(backgroundColor),
|
|
||||||
elevation: Dp = AppBarDefaults.TopAppBarElevation
|
|
||||||
) {
|
|
||||||
AppBar(
|
|
||||||
backgroundColor,
|
|
||||||
contentColor,
|
|
||||||
elevation,
|
|
||||||
AppBarDefaults.ContentPadding,
|
|
||||||
RectangleShape,
|
|
||||||
modifier
|
|
||||||
) {
|
|
||||||
if (navigationIcon == null) {
|
|
||||||
Spacer(Modifier.width(16.dp - 4.dp))
|
|
||||||
} else {
|
|
||||||
Row(Modifier.fillMaxHeight().width(72.dp - 4.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalContentAlpha provides ContentAlpha.high,
|
|
||||||
content = navigationIcon
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
Modifier.fillMaxHeight().weight(1f),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.h6) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalContentAlpha provides ContentAlpha.high,
|
|
||||||
content = title
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
|
||||||
Row(
|
|
||||||
Modifier.fillMaxHeight(),
|
|
||||||
horizontalArrangement = Arrangement.End,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
content = actions
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AppBar(
|
|
||||||
backgroundColor: Color,
|
|
||||||
contentColor: Color,
|
|
||||||
elevation: Dp,
|
|
||||||
contentPadding: PaddingValues,
|
|
||||||
shape: Shape,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: @Composable RowScope.() -> Unit
|
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
color = backgroundColor,
|
|
||||||
contentColor = contentColor,
|
|
||||||
elevation = elevation,
|
|
||||||
shape = shape,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
|
||||||
Row(
|
|
||||||
Modifier.fillMaxWidth()
|
|
||||||
.padding(contentPadding)
|
|
||||||
.height(56.dp),
|
|
||||||
horizontalArrangement = Arrangement.Start,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
fun MyOutlinedTextFieldWithSuffix(
|
fun MyOutlinedTextFieldWithSuffix(
|
||||||
@@ -142,11 +58,11 @@ fun MyOutlinedTextFieldWithSuffix(
|
|||||||
Gray500
|
Gray500
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Column() {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = value,
|
value = value,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
|
|
||||||
.background(colors.backgroundColor(enabled).value, shape)
|
.background(colors.backgroundColor(enabled).value, shape)
|
||||||
.defaultMinSize(
|
.defaultMinSize(
|
||||||
minWidth = TextFieldDefaults.MinWidth,
|
minWidth = TextFieldDefaults.MinWidth,
|
||||||
@@ -168,9 +84,11 @@ fun MyOutlinedTextFieldWithSuffix(
|
|||||||
value = value,
|
value = value,
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
innerTextField = {
|
innerTextField = {
|
||||||
Row {
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
val alignModifier = Modifier.alignByBaseline()
|
val alignModifier = Modifier.alignByBaseline()
|
||||||
Box(alignModifier.weight(1f)){
|
Box(alignModifier){
|
||||||
innerTextField()
|
innerTextField()
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
@@ -207,4 +125,5 @@ fun MyOutlinedTextFieldWithSuffix(
|
|||||||
color = helperTextColor,
|
color = helperTextColor,
|
||||||
modifier = Modifier.padding(start = 16.dp)
|
modifier = Modifier.padding(start = 16.dp)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ class AddBatteryViewModel(application: Application): AndroidViewModel(applicatio
|
|||||||
var batteryName by mutableStateOf("")
|
var batteryName by mutableStateOf("")
|
||||||
var batteryHasError by mutableStateOf(false)
|
var batteryHasError by mutableStateOf(false)
|
||||||
var batteryHelperId by mutableStateOf(R.string.helper_required)
|
var batteryHelperId by mutableStateOf(R.string.helper_required)
|
||||||
|
var switchAutoCap by mutableStateOf(true)
|
||||||
|
|
||||||
var declaredCapacity by mutableStateOf("")
|
var declaredCapacity by mutableStateOf("")
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.sockenklaus.batterytracker.ui.models
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.*
|
||||||
|
import com.sockenklaus.batterytracker.room.BatteryTrackerDB
|
||||||
|
import com.sockenklaus.batterytracker.room.entities.Battery
|
||||||
|
import com.sockenklaus.batterytracker.room.entities.Charge
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class BatteryDetailsViewModel(
|
||||||
|
application: Application,
|
||||||
|
state: SavedStateHandle
|
||||||
|
) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val db = BatteryTrackerDB.getInstance(application)
|
||||||
|
|
||||||
|
val battery: Flow<Battery> = if(state.get<Int?>("batteryId") !== null) {
|
||||||
|
db.batteryDao().getBatteryById(state["batteryId"]!!)
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(Battery(name=""))
|
||||||
|
}
|
||||||
|
|
||||||
|
val charges: Flow<List<Charge>> = if(state.get<Int?>("batteryId") !== null){
|
||||||
|
db.chargeDao().getChargesByBatteryId(state["batteryId"]!!)
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
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.minOfOrNull { it.charge }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
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.maxOfOrNull { it.charge }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,16 @@ 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.compose.ui.platform.LocalContext
|
||||||
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.room.BatteryTrackerDB
|
||||||
|
import com.sockenklaus.batterytracker.room.entities.Battery
|
||||||
import com.sockenklaus.batterytracker.ui.Routes
|
import com.sockenklaus.batterytracker.ui.Routes
|
||||||
import com.sockenklaus.batterytracker.ui.ToggleDrawerButton
|
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
var appTitle by mutableStateOf("Home")
|
|
||||||
var currentScreen by mutableStateOf(Routes.HOME)
|
var currentScreen by mutableStateOf(Routes.HOME)
|
||||||
|
|
||||||
lateinit var navController: NavHostController
|
lateinit var navController: NavHostController
|
||||||
@@ -23,4 +22,20 @@ class MainViewModel : ViewModel() {
|
|||||||
navController = rememberNavController()
|
navController = rememberNavController()
|
||||||
scaffoldState = rememberScaffoldState()
|
scaffoldState = rememberScaffoldState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getBatteryName(
|
||||||
|
id: Int?
|
||||||
|
) : String {
|
||||||
|
val db = BatteryTrackerDB.getInstance(LocalContext.current)
|
||||||
|
val battery = id?.let { db.batteryDao().getBatteryById(it).collectAsState(initial = Battery(name="")) }
|
||||||
|
|
||||||
|
var name = ""
|
||||||
|
|
||||||
|
if (battery != null){
|
||||||
|
name = battery.value.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ ext {
|
|||||||
}
|
}
|
||||||
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '7.2.1' apply false
|
id 'com.android.application' version '7.3.1' apply false
|
||||||
id 'com.android.library' version '7.2.1' apply false
|
id 'com.android.library' version '7.3.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
|
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
|
||||||
id 'org.jetbrains.kotlin.kapt' version '1.7.0' apply false
|
id 'org.jetbrains.kotlin.kapt' version '1.7.0' apply false
|
||||||
}
|
}
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Sun Jul 10 23:29:55 CEST 2022
|
#Sun Jul 10 23:29:55 CEST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Reference in New Issue
Block a user