Moved to complete Compose layout, rewrote all composables, removed all xml layouts. Changed navigation component.

This commit is contained in:
sockenklaus
2022-07-21 03:30:34 +02:00
parent 8f691b30d8
commit f637007e64
32 changed files with 818 additions and 767 deletions

View File

@@ -67,39 +67,46 @@ android {
dependencies {
implementation 'androidx.room:room-runtime:2.4.2'
implementation "androidx.compose.ui:ui:1.3.0-alpha01"
implementation "androidx.compose.ui:ui-tooling-preview:1.3.0-alpha01"
implementation 'androidx.compose.material:material-icons-extended:1.3.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
kapt 'androidx.room:room-compiler:2.4.2'
implementation 'androidx.room:room-rxjava3:2.4.2'
implementation "androidx.room:room-paging:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
kapt 'androidx.room:room-compiler:2.4.2'
implementation "androidx.compose.ui:ui:1.3.0-alpha01"
implementation "androidx.compose.ui:ui-tooling-preview:1.3.0-alpha01"
implementation 'androidx.compose.material:material-icons-extended:1.3.0-alpha01'
implementation 'androidx.compose.material:material:1.3.0-alpha01'
implementation 'androidx.compose.runtime:runtime-livedata:1.1.1'
implementation 'androidx.compose.animation:animation:1.3.0-alpha01'
implementation 'androidx.compose.ui:ui-tooling:1.3.0-alpha01'
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.3.0-alpha01'
debugImplementation "androidx.compose.ui:ui-test-manifest:1.1.1"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0'
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-compose:1.0.1"
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
implementation "androidx.navigation:navigation-compose:2.5.0"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.activity:activity-compose:1.5.0'
implementation 'androidx.compose.material:material:1.3.0-alpha01'
implementation 'androidx.compose.animation:animation:1.3.0-alpha01'
implementation 'androidx.compose.ui:ui-tooling:1.3.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0'
implementation 'com.google.android.material:compose-theme-adapter:1.1.14'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.3.0-alpha01'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
debugImplementation "androidx.compose.ui:ui-test-manifest:1.1.1"
}

View File

@@ -0,0 +1,149 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "e86f0253ec49cbc67a601f90d5169a8a",
"entities": [
{
"tableName": "charges",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `charge` REAL NOT NULL, `battery_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `comment` TEXT NOT NULL DEFAULT '', `created_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, `synced` INTEGER NOT NULL DEFAULT false, FOREIGN KEY(`battery_id`) REFERENCES `batteries`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "charge",
"columnName": "charge",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "batteryId",
"columnName": "battery_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "comment",
"columnName": "comment",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "CURRENT_TIMESTAMP"
},
{
"fieldPath": "synced",
"columnName": "synced",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_charges_battery_id",
"unique": false,
"columnNames": [
"battery_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_charges_battery_id` ON `${TABLE_NAME}` (`battery_id`)"
}
],
"foreignKeys": [
{
"table": "batteries",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"battery_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "batteries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `declared_capacity` REAL DEFAULT NULL, `comment` TEXT NOT NULL DEFAULT '', `synced` INTEGER NOT NULL DEFAULT false, `created_at` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "declaredCapacity",
"columnName": "declared_capacity",
"affinity": "REAL",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "comment",
"columnName": "comment",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "synced",
"columnName": "synced",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "CURRENT_TIMESTAMP"
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e86f0253ec49cbc67a601f90d5169a8a')"
]
}
}

View File

@@ -13,16 +13,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.BatteryTracker"
tools:targetApi="31">
<activity
android:name=".AddBattery"
android:exported="false"
android:label="@string/title_activity_add_battery"
android:theme="@style/Theme.BatteryTracker" />
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.BatteryTracker.NoActionBar">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -1,56 +1,20 @@
package com.sockenklaus.batterytracker
import android.os.Bundle
import android.view.Menu
import com.google.android.material.navigation.NavigationView
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.drawerlayout.widget.DrawerLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import com.sockenklaus.batterytracker.databinding.ActivityMainBinding
import com.sockenklaus.batterytracker.room.BatteryTrackerDB
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.view.WindowCompat
import com.sockenklaus.batterytracker.ui.BatteryTracker
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
val db: BatteryTrackerDB by lazy { BatteryTrackerDB.getInstance(this) }
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
WindowCompat.setDecorFitsSystemWindows(window, true)
setSupportActionBar(binding.appBarMain.toolbar)
val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
navController = findNavController(R.id.nav_host_fragment_content_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_home, R.id.nav_add_charge, R.id.nav_add_battery, R.id.nav_settings
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
setContent {
BatteryTracker()
}
}
}

View File

@@ -12,7 +12,7 @@ import com.sockenklaus.batterytracker.room.entities.Charge
@Database(
entities = [Charge::class, Battery::class],
version = 3
version = 4
)
@TypeConverters(Converters::class)
abstract class BatteryTrackerDB : RoomDatabase() {

View File

@@ -2,11 +2,22 @@ package com.sockenklaus.batterytracker.room
import androidx.room.TypeConverter
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
class Converters {
@TypeConverter
fun toLocalDate(value: Long): LocalDate {
return LocalDate.ofEpochDay(value)
}
@TypeConverter
fun toEpochDay(value: LocalDate): Long {
return value.toEpochDay()
}
@TypeConverter
fun toLocalDateTime(value: Long): LocalDateTime {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneId.systemDefault())

View File

@@ -1,6 +1,7 @@
package com.sockenklaus.batterytracker.room.entities
import androidx.room.*
import java.time.LocalDate
import java.time.LocalDateTime
@Entity(
@@ -24,7 +25,7 @@ data class Charge(
@ColumnInfo(name = "battery_id")
val batteryId: Int,
val date: LocalDateTime,
val date: LocalDate,
@ColumnInfo(defaultValue = "")
val comment: String = "",

View File

@@ -0,0 +1,150 @@
package com.sockenklaus.batterytracker.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.ui.composables.AddBattery
import com.sockenklaus.batterytracker.ui.composables.AddCharge
import com.sockenklaus.batterytracker.ui.composables.Home
import com.sockenklaus.batterytracker.ui.models.MainViewModel
import kotlinx.coroutines.launch
@Composable
fun BatteryTracker() {
MaterialTheme {
val state: MainViewModel = viewModel()
state.init()
Scaffold(
scaffoldState = state.scaffoldState,
topBar = {
if(state.showAppBar){
TopAppBar (
title = { AppBarTitle(state = state) },
navigationIcon = {
ToggleDrawerButton(
state = state,
)
}
)
}
},
) { innerPadding ->
ModalDrawer(
drawerState = state.scaffoldState.drawerState,
drawerContent = {
NavListItem(
textId = R.string.nav_home,
icon = Icons.Default.Home,
route = "home",
state = state,
)
NavListItem(
icon = Icons.Default.BatteryChargingFull,
textId = R.string.nav_add_charge,
route = "add_charge",
state = state,
)
NavListItem(
icon = Icons.Default.BatteryFull,
textId = R.string.nav_add_battery,
route = "add_battery",
state = state,
)
}
) {
NavHost(
navController = state.navController,
startDestination = "home",
modifier = Modifier.padding(innerPadding))
{
composable("home") { Home(state.navController) }
composable("add_charge") { AddCharge(state.navController) }
composable("add_battery") { AddBattery(state.navController) }
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class)
@Composable
fun AppBarTitle(
state: MainViewModel
){
val text = if(state.scaffoldState.drawerState.targetValue == DrawerValue.Closed) {
state.appTitle
} else {
stringResource(R.string.nav_header_title)
}
AnimatedContent(targetState = text) {
Text(it)
}
}
@Composable
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
fun ToggleDrawerButton(
state: MainViewModel
){
val scope = rememberCoroutineScope()
val icon = if(state.scaffoldState.drawerState.targetValue == DrawerValue.Open) {
Icons.Default.Close
} else {
Icons.Default.Menu
}
IconButton(
onClick = {
if(state.scaffoldState.drawerState.isClosed) scope.launch { state.scaffoldState.drawerState.open() }
else scope.launch { state.scaffoldState.drawerState.close() }
}
) {
AnimatedContent(targetState = icon) { targetState ->
Icon(targetState, null)
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NavListItem(
icon: ImageVector,
textId: Int?,
route: String,
state: MainViewModel
) {
val scope = rememberCoroutineScope()
val text = if ( textId != null) stringResource(textId) else ""
ListItem(
icon = { Icon(icon, null) },
text = { Text(text) },
modifier = Modifier.clickable {
scope.launch {
state.appTitle = text
state.navController.navigate(route)
state.scaffoldState.drawerState.close()
}
}.wrapContentWidth()
)
}

View File

@@ -0,0 +1,79 @@
package com.sockenklaus.batterytracker.ui.composables
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.ui.composables.util.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.models.AddBatteryViewModel
import com.sockenklaus.batterytracker.util.validateDecimal
@Composable
fun AddBattery(navController: NavController){
val model: AddBatteryViewModel = viewModel()
val batteries by model.batteries.observeAsState(emptyList())
val outerPadding = 24.dp
val innerPadding = 16.dp
Column(
Modifier.padding(outerPadding)
) {
MyOutlinedTextFieldWithSuffix(
value = model.batteryName,
onValueChange = { value ->
model.batteryName = value
model.batteryHasError = false
model.batteryHelperId = R.string.helper_required
if(batteries.any{ it.name.equals(value, ignoreCase = true) }){
model.batteryHasError = true
model.batteryHelperId = R.string.helper_battery_not_unique
}
},
labelId = R.string.hint_enter_battery_name,
leadingIcon = { Icon(Icons.Default.Tag, "Icon Tag") },
isError = model.batteryHasError,
helperTextId = model.batteryHelperId
)
Spacer(Modifier.size(innerPadding))
MyOutlinedTextFieldWithSuffix(
value = model.declaredCapacity,
onValueChange = {
model.declaredCapacity = validateDecimal(it, model.declaredCapacity)
},
labelId = R.string.hint_enter_declared_capacity,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") },
suffix = "Ah"
)
Spacer(Modifier.size(outerPadding))
ExtendedFloatingActionButton(
onClick = {
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") },
text = { Text(stringResource(R.string.button_save_battery)) },
modifier = Modifier.align(Alignment.End)
)
}
}

View File

@@ -0,0 +1,179 @@
package com.sockenklaus.batterytracker.ui.composables
import android.app.DatePickerDialog
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BatteryChargingFull
import androidx.compose.material.icons.filled.EditCalendar
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Tag
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.ui.composables.util.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.models.AddChargeViewModel
import com.sockenklaus.batterytracker.ui.theme.Gray500
import com.sockenklaus.batterytracker.util.validateDecimal
import java.time.*
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AddCharge(navController: NavController){
val outerPadding = 24.dp
val innerPadding = 16.dp
var bIdExpanded by remember { mutableStateOf(false)}
val model: AddChargeViewModel = viewModel()
val batteries by model.batteries.observeAsState(emptyList())
Column(
Modifier.padding(outerPadding)
) {
ExposedDropdownMenuBox(
expanded = bIdExpanded,
onExpandedChange = { bIdExpanded = !bIdExpanded}
) {
val filteringOptions = batteries.filter { it.name.contains(model.batteryId.text, ignoreCase = true)}
OutlinedTextField(
value = model.batteryId,
onValueChange = {
model.batteryId = it
model.batteryHasError = false
model.batteryHelper = R.string.helper_required
bIdExpanded = filteringOptions.size > 1
},
label = { Text(stringResource(R.string.select_battery_id)) },
leadingIcon = { Icon(Icons.Default.Tag, null) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = bIdExpanded)
},
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier.fillMaxWidth(),
isError = model.batteryHasError
)
if(filteringOptions.isNotEmpty()) {
ExposedDropdownMenu(
expanded = bIdExpanded,
onDismissRequest = { bIdExpanded = false }
) {
for (filteringOption in filteringOptions) {
DropdownMenuItem(
onClick = {
model.batteryId = TextFieldValue(
text = filteringOption.name,
selection = TextRange(filteringOption.name.length)
)
bIdExpanded = false
}
) {
Text(filteringOption.name)
}
}
}
}
}
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))
MyOutlinedTextFieldWithSuffix(
value = model.charge,
onValueChange = {
model.charge = validateDecimal(it, model.charge)
model.chargeHasError = false
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
labelId = R.string.hint_charge,
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Charging Full") },
isError = model.chargeHasError,
helperTextId = R.string.helper_required,
suffix = "Ah"
)
Spacer(Modifier.size(innerPadding))
ChargeDatePicker(
date = model.date,
onSelect = { model.date = it}
)
Spacer(Modifier.size(outerPadding))
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.button_save_charge)) },
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)){
navController.navigate("home")
}
},
icon = { Icon(Icons.Default.Save, "Icon Save") },
modifier = Modifier.align(Alignment.End)
)
}
}
@Composable
fun ChargeDatePicker(
date: LocalDate,
onSelect: (LocalDate) -> Unit,
){
val outputFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(Locale.GERMANY)
val datePickerDialog = DatePickerDialog(
LocalContext.current
)
datePickerDialog.setOnDateSetListener { _, year, month, day ->
onSelect(LocalDate.of(year, month + 1, day))
}
datePickerDialog.updateDate(date.year, date.monthValue - 1, date.dayOfMonth)
TextButton(
onClick = {
datePickerDialog.show()
},
modifier = Modifier.fillMaxWidth()
){
Icon(
Icons.Default.EditCalendar,
"Edit Calendar Icon",
Modifier.size(ButtonDefaults.IconSize),
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Date: ${date.format(outputFormat)}")
}
}

View File

@@ -0,0 +1,85 @@
package com.sockenklaus.batterytracker.ui.composables
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.composables.util.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.models.HomeViewModel
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Home(navController: NavController) {
val model: HomeViewModel = viewModel()
val batteries by model.batteries.observeAsState(emptyList<Battery>())
var filterText by remember { mutableStateOf("")}
val filteredList = batteries.filter { it.name.contains(filterText, ignoreCase = true) }
val modHorizontalPadding = Modifier.padding(horizontal = 16.dp)
Column {
MyOutlinedTextFieldWithSuffix(
value = filterText,
onValueChange = { filterText = it },
labelId = R.string.hint_filter_batteries,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
top = 16.dp
)
)
LazyColumn(
state = LazyListState()
) {
items(filteredList){ battery ->
ListItem(
text = { Text(battery.name) },
secondaryText = {
if(battery.declaredCapacity != null){
Text("Capacity: ${battery.declaredCapacity} Ah")
}
}
)
Divider(modHorizontalPadding)
}
}
}
}
/*
class HomeFragment : Fragment() {
@OptIn(ExperimentalMaterialApi::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
BatteryTrackerTheme() {
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
}
}*/

View File

@@ -1,4 +1,4 @@
package com.sockenklaus.batterytracker.ui.composables
package com.sockenklaus.batterytracker.ui.composables.util
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -9,18 +9,104 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.TextFieldDefaults.OutlinedTextFieldDecorationBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
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
@OptIn(ExperimentalMaterialApi::class)
fun MyOutlinedTextFieldWithSuffix(
value: String,
onValueChange: (String) -> Unit,
@@ -56,10 +142,10 @@ fun MyOutlinedTextFieldWithSuffix(
Gray500
}
@OptIn(ExperimentalMaterialApi::class)
BasicTextField(
value = value,
modifier = modifier.padding(top = 8.dp)
modifier = modifier
.padding(top = 8.dp)
.background(colors.backgroundColor(enabled).value, shape)
.defaultMinSize(

View File

@@ -1,130 +0,0 @@
package com.sockenklaus.batterytracker.ui.fragments.add_battery
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.composables.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.theme.BatteryTrackerTheme
import com.sockenklaus.batterytracker.util.validateDecimal
/**
* A simple [Fragment] subclass.
* Use the [AddBatteryFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class AddBatteryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val model = ViewModelProvider(this)[AddBatteryViewModel::class.java]
var batteries by mutableStateOf(emptyList<Battery>())
model.batteries.observe(viewLifecycleOwner) {
batteries = it
}
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
BatteryTrackerTheme() {
val outerPadding = 24.dp
val innerPadding = 16.dp
Column(
Modifier.padding(outerPadding)
) {
MyOutlinedTextFieldWithSuffix(
value = model.batteryName,
onValueChange = { value ->
model.batteryName = value
model.batteryHasError = false
model.batteryHelperId = R.string.helper_required
if(batteries.any{ it.name.equals(value, ignoreCase = true) }){
model.batteryHasError = true
model.batteryHelperId = R.string.helper_battery_not_unique
}
},
labelId = R.string.hint_enter_battery_name,
leadingIcon = { Icon(Icons.Default.Tag, "Icon Tag") },
isError = model.batteryHasError,
helperTextId = model.batteryHelperId
)
Spacer(Modifier.size(innerPadding))
MyOutlinedTextFieldWithSuffix(
value = model.declaredCapacity,
onValueChange = {
model.declaredCapacity = validateDecimal(it, model.declaredCapacity)
},
labelId = R.string.hint_enter_declared_capacity,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
leadingIcon = { Icon(Icons.Default.BatteryFull, "Icon Battery Full") },
suffix = "Ah"
)
Spacer(Modifier.size(outerPadding))
ExtendedFloatingActionButton(
onClick = {
if(model.batteryName.isBlank()) model.batteryHasError = true
if(!model.batteryHasError && model.saveBattery(model.batteryName, model.declaredCapacity)){
findNavController().navigate(R.id.action_nav_add_battery_to_nav_home)
}
},
icon = { Icon(Icons.Default.Save, "Icon Save") },
text = { Text(stringResource(R.string.button_save_battery)) },
modifier = Modifier.align(Alignment.End)
)
}
}
}
}
}
/*companion object {
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment AddBatteryFragment.
*//*
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
AddBatteryFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}*/
}

View File

@@ -1,212 +0,0 @@
package com.sockenklaus.batterytracker.ui.fragments.add_charge
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BatteryChargingFull
import androidx.compose.material.icons.filled.EditCalendar
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Tag
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import com.google.android.material.datepicker.MaterialDatePicker
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.composables.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.theme.BatteryTrackerTheme
import com.sockenklaus.batterytracker.ui.theme.Gray500
import com.sockenklaus.batterytracker.util.validateDecimal
import java.time.*
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
class AddChargeFragment : Fragment() {
private lateinit var model: AddChargeViewModel
@OptIn(ExperimentalMaterialApi::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
model = ViewModelProvider(this)[AddChargeViewModel::class.java]
var batteries by mutableStateOf(emptyList<Battery>())
model.batteries.observe(this.viewLifecycleOwner){
batteries = it
}
return ComposeView(requireContext()).apply{
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
BatteryTrackerTheme() {
val outerPadding = 24.dp
val innerPadding = 16.dp
var bIdExpanded by remember { mutableStateOf(false)}
Column(
Modifier.padding(outerPadding)
) {
ExposedDropdownMenuBox(
expanded = bIdExpanded,
onExpandedChange = { bIdExpanded = !bIdExpanded}
) {
val filteringOptions = batteries.filter { it.name.contains(model.batteryId.text, ignoreCase = true)}
OutlinedTextField(
value = model.batteryId,
onValueChange = {
model.batteryId = it
model.batteryHasError = false
model.batteryHelper = R.string.helper_required
bIdExpanded = filteringOptions.size > 1
},
label = { Text(stringResource(R.string.select_battery_id)) },
leadingIcon = { Icon(Icons.Default.Tag, null) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = bIdExpanded)
},
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier.fillMaxWidth(),
isError = model.batteryHasError
)
if(filteringOptions.isNotEmpty()) {
ExposedDropdownMenu(
expanded = bIdExpanded,
onDismissRequest = { bIdExpanded = false }
) {
for (filteringOption in filteringOptions) {
DropdownMenuItem(
onClick = {
model.batteryId = TextFieldValue(
text = filteringOption.name,
selection = TextRange(filteringOption.name.length)
)
bIdExpanded = false
}
) {
Text(filteringOption.name)
}
}
}
}
}
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))
MyOutlinedTextFieldWithSuffix(
value = model.charge,
onValueChange = {
model.charge = validateDecimal(it, model.charge)
model.chargeHasError = false
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
labelId = R.string.hint_charge,
leadingIcon = { Icon(Icons.Default.BatteryChargingFull, "Icon Battery Charging Full") },
isError = model.chargeHasError,
helperTextId = R.string.helper_required,
suffix = "Ah"
)
Spacer(Modifier.size(innerPadding))
ChargeDatePicker(
date = model.date,
onSelect = { model.date = it}
)
Spacer(Modifier.size(outerPadding))
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.button_save_charge)) },
onClick = {
if(!batteries.any{ it.name == model.batteryId.text }){
model.batteryHasError = true
model.batteryHelper = R.string.helper_battery_not_found
}
if(model.batteryId.text.isBlank()){
model.batteryHasError = true
model.batteryHelper = R.string.helper_required
}
if(model.charge.isBlank()){
model.chargeHasError = true
}
if(!model.batteryHasError && !model.chargeHasError && model.saveCharge(batteries, model.batteryId.text, model.charge, model.date)){
findNavController().navigate(R.id.action_nav_add_charge_to_nav_home)
}
},
icon = { Icon(Icons.Default.Save, "Icon Save") },
modifier = Modifier.align(Alignment.End)
)
}
}
}
}
}
@Composable
fun ChargeDatePicker(
date: LocalDateTime,
onSelect: (LocalDateTime) -> Unit,
) {
val dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(Locale.GERMANY)
val datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select date")
.setSelection( date.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() )
.build()
datePicker.addOnPositiveButtonClickListener {
onSelect(LocalDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault()))
}
TextButton(
onClick = {
datePicker.show(parentFragmentManager, "tag")
},
modifier = Modifier.fillMaxWidth()
){
Icon(
Icons.Default.EditCalendar,
"Edit Calendar Icon",
Modifier.size(ButtonDefaults.IconSize),
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Date: ${date.format(dateFormat)}")
}
}
override fun onDestroyView() {
super.onDestroyView()
}
}

View File

@@ -1,89 +0,0 @@
package com.sockenklaus.batterytracker.ui.fragments.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.lifecycle.viewmodel.compose.viewModel
import com.sockenklaus.batterytracker.R
import com.sockenklaus.batterytracker.room.entities.Battery
import com.sockenklaus.batterytracker.ui.composables.MyOutlinedTextFieldWithSuffix
import com.sockenklaus.batterytracker.ui.theme.BatteryTrackerTheme
class HomeFragment : Fragment() {
@OptIn(ExperimentalMaterialApi::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
BatteryTrackerTheme() {
val model: HomeViewModel = viewModel()
var batteries by remember { mutableStateOf(emptyList<Battery>()) }
var filterText by remember { mutableStateOf("")}
model.batteries.observe(viewLifecycleOwner) {
batteries = it
}
val filteredList = batteries.filter { it.name.contains(filterText, ignoreCase = true) }
val modHorizontalPadding = Modifier.padding(horizontal = 16.dp)
Column {
MyOutlinedTextFieldWithSuffix(
value = filterText,
onValueChange = { filterText = it },
labelId = R.string.hint_filter_batteries,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
top = 16.dp
)
)
LazyColumn(
state = LazyListState()
) {
items(filteredList){ battery ->
ListItem(
text = { Text(battery.name) },
secondaryText = {
if(battery.declaredCapacity != null){
Text("Capacity: ${battery.declaredCapacity} Ah")
}
}
)
Divider(modHorizontalPadding)
}
}
}
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
}
}

View File

@@ -1,27 +0,0 @@
package com.sockenklaus.batterytracker.ui.fragments.settings
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.sockenklaus.batterytracker.R
class SettingsFragment : Fragment() {
companion object {
fun newInstance() = SettingsFragment()
}
private lateinit var viewModel: SettingsViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
return inflater.inflate(R.layout.fragment_settings, container, false)
}
}

View File

@@ -1,7 +0,0 @@
package com.sockenklaus.batterytracker.ui.fragments.settings
import androidx.lifecycle.ViewModel
class SettingsViewModel : ViewModel() {
// TODO: Implement the ViewModel
}

View File

@@ -1,4 +1,4 @@
package com.sockenklaus.batterytracker.ui.fragments.add_battery
package com.sockenklaus.batterytracker.ui.models
import android.app.Application
import androidx.compose.runtime.getValue
@@ -16,7 +16,6 @@ import java.lang.Exception
class AddBatteryViewModel(application: Application): AndroidViewModel(application) {
private val app = application
private val db = BatteryTrackerDB.getInstance(application)
val batteries = db.batteryDao().getBatteries().asLiveData()

View File

@@ -1,4 +1,4 @@
package com.sockenklaus.batterytracker.ui.fragments.add_charge
package com.sockenklaus.batterytracker.ui.models
import android.app.Application
import androidx.compose.runtime.getValue
@@ -15,7 +15,7 @@ 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.LocalDate
class AddChargeViewModel(application: Application) : AndroidViewModel(application) {
@@ -23,7 +23,7 @@ class AddChargeViewModel(application: Application) : AndroidViewModel(applicatio
var batteries: LiveData<List<Battery>> = db.batteryDao().getBatteries().asLiveData()
var batteryId by mutableStateOf(TextFieldValue(""))
var date: LocalDateTime by mutableStateOf(LocalDateTime.now())
var date: LocalDate by mutableStateOf(LocalDate.now())
var charge by mutableStateOf("")
var batteryHasError by mutableStateOf(false)
@@ -35,7 +35,7 @@ class AddChargeViewModel(application: Application) : AndroidViewModel(applicatio
batteryList: List<Battery>,
batteryName: String,
charge: String,
date: LocalDateTime
date: LocalDate
):Boolean {
val battery = batteryList.find { it.name == batteryName }

View File

@@ -1,4 +1,4 @@
package com.sockenklaus.batterytracker.ui.fragments.home
package com.sockenklaus.batterytracker.ui.models
import android.app.Application
import androidx.lifecycle.*

View File

@@ -0,0 +1,21 @@
package com.sockenklaus.batterytracker.ui.models
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
class MainViewModel : ViewModel() {
var showAppBar by mutableStateOf(true)
var appTitle by mutableStateOf("Home")
lateinit var navController: NavHostController
lateinit var scaffoldState: ScaffoldState
@Composable
fun init(){
navController = rememberNavController()
scaffoldState = rememberScaffoldState()
}
}

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.BatteryTracker.AppBarOverlay">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.BatteryTracker.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/content_main"
layout="@layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main">
<fragment
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragments.settings.SettingsFragment">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Hello"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp"
android:contentDescription="@string/button_save_settings"
android:text="@string/button_save_settings"
app:icon="@drawable/ic_baseline_check_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceSubtitle1">
</TextView>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/nav_header_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
/>
</LinearLayout>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_home"
android:icon="@drawable/ic_baseline_home_24"
android:title="@string/menu_home" />
<item
android:id="@+id/nav_add_charge"
android:title="@string/menu_add_charge"
android:icon="@drawable/ic_baseline_battery_charging_full_24"
/>
<item
android:id="@+id/nav_add_battery"
android:title="@string/menu_add_battery"
android:icon="@drawable/ic_baseline_battery_5_bar_24"
/>
<item
android:id="@+id/nav_settings"
android:icon="@drawable/ic_baseline_settings_24"
android:title="@string/menu_settings" />
</group>
</menu>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- <item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />-->
</menu>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/nav_home">
<fragment
android:id="@+id/nav_home"
android:name="com.sockenklaus.batterytracker.ui.fragments.home.HomeFragment"
android:label="@string/menu_home" />
<fragment
android:id="@+id/nav_add_charge"
android:name="com.sockenklaus.batterytracker.ui.fragments.add_charge.AddChargeFragment"
android:label="@string/menu_add_charge">
<action
android:id="@+id/action_nav_add_charge_to_nav_home"
app:destination="@id/nav_home" />
</fragment>
<fragment
android:id="@+id/nav_add_battery"
android:name="com.sockenklaus.batterytracker.ui.fragments.add_battery.AddBatteryFragment"
android:label="@string/menu_add_battery" >
<action
android:id="@+id/action_nav_add_battery_to_nav_home"
app:destination="@id/nav_home" />
</fragment>
<fragment
android:id="@+id/nav_settings"
android:name="com.sockenklaus.batterytracker.ui.fragments.settings.SettingsFragment"
android:label="@string/action_settings"
tools:layout="@layout/fragment_settings" />
</navigation>

View File

@@ -1,21 +1,19 @@
<resources>
<string name="app_name">BatteryTracker</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">Navigation</string>
<string name="nav_header_subtitle">android.studio@android.com</string>
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<string name="menu_home">Home</string>
<string name="menu_gallery">Gallery</string>
<string name="menu_slideshow">Slideshow</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="menu_settings">Settings</string>
<string name="button_save_settings">Save Settings</string>
<string name="menu_add_charge">Add Charge</string>
<string name="menu_add_battery">Add Battery</string>
<string name="nav_header_title">Navigation</string>
<string name="nav_home">Home</string>
<string name="nav_add_charge">Add Charge</string>
<string name="nav_add_battery">Add Battery</string>
<string name="battery_id">Battery ID</string>
<string name="button_save_charge">Save Charge</string>
<string name="date">Date</string>
@@ -31,4 +29,5 @@
<string name="helper_battery_not_unique">Battery-Name not unique!</string>
<string name="helper_battery_not_found">Battery not found</string>
<string name="hint_filter_batteries">Filter Batteries…</string>
<string name="title_activity_main2">MainActivity2</string>
</resources>

View File

@@ -1,5 +1,8 @@
buildscript {
ext {
compose_version = '1.1.0-beta01'
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.2.1' apply false