Compare commits

...

2 Commits

Author SHA1 Message Date
3c8c5a38c0 f 2023-08-05 19:17:16 +02:00
dfa41e9c94 Before gradle plugin update 2023-07-22 21:25:44 +02:00
16 changed files with 383 additions and 34 deletions

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,6 +1,7 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
} }
android { android {
@ -9,7 +10,7 @@ android {
defaultConfig { defaultConfig {
applicationId "de.polyfish0.fitnessjourney" applicationId "de.polyfish0.fitnessjourney"
minSdk 24 minSdk 26
targetSdk 33 targetSdk 33
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@ -47,6 +48,31 @@ android {
} }
dependencies { dependencies {
def room_version = "2.5.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// To use Kotlin annotation processing tool (kapt)
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava2 support for Room
//implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
//implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
//implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
// optional - Paging 3 Integration
//implementation "androidx.room:room-paging:$room_version"
implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.0" implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.0"
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.9.0'
implementation "androidx.navigation:navigation-compose:2.5.3" implementation "androidx.navigation:navigation-compose:2.5.3"
@ -60,6 +86,8 @@ dependencies {
implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3' implementation 'androidx.compose.material3:material3'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
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.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@ -3,6 +3,7 @@ package de.polyfish0.fitnessjourney
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
@ -25,6 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@ -32,6 +34,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import de.polyfish0.fitnessjourney.routes.MainScreenRoute import de.polyfish0.fitnessjourney.routes.MainScreenRoute
import de.polyfish0.fitnessjourney.routes.Routes import de.polyfish0.fitnessjourney.routes.Routes
import de.polyfish0.fitnessjourney.routes.bodycomposition.AddBodyCompositionRoute
import de.polyfish0.fitnessjourney.routes.bodycomposition.BodyCompositionMainRoute import de.polyfish0.fitnessjourney.routes.bodycomposition.BodyCompositionMainRoute
import de.polyfish0.fitnessjourney.routes.settings.SettingsMainRoute import de.polyfish0.fitnessjourney.routes.settings.SettingsMainRoute
import de.polyfish0.fitnessjourney.routes.trainingplans.TrainingPlansRoute import de.polyfish0.fitnessjourney.routes.trainingplans.TrainingPlansRoute
@ -112,6 +115,13 @@ fun MainCompose(
) )
} }
) { padding -> ) { padding ->
SideMenuComposable(navController = navController, padding = padding)
}
}
}
@Composable
fun SideMenuComposable(navController: NavHostController, padding: PaddingValues) {
Surface(modifier = Modifier.padding(padding)) { Surface(modifier = Modifier.padding(padding)) {
NavHost( NavHost(
navController = navController, navController = navController,
@ -133,8 +143,18 @@ fun MainCompose(
composable(Routes.SETTINGS) { composable(Routes.SETTINGS) {
SettingsMainRoute(navigation = navController) SettingsMainRoute(navigation = navController)
} }
composable(Routes.ADDING_BODY_COMPOSITION) {
AddBodyCompositionRoute(navigation = navController)
} }
} }
} }
} }
@Composable
@Preview
fun PreviewMainComposable() {
FitnessJourneyTheme {
MainCompose()
}
} }

View File

@ -0,0 +1,13 @@
package de.polyfish0.fitnessjourney.database
import androidx.room.Database
import androidx.room.RoomDatabase
import de.polyfish0.fitnessjourney.database.dao.BodyDataDao
import de.polyfish0.fitnessjourney.database.dao.PicturesDAO
import de.polyfish0.fitnessjourney.database.entities.Pictures
@Database(entities = [Pictures::class], version = 1)
abstract class FitnessDatabase : RoomDatabase() {
abstract fun picturesDao(): PicturesDAO
abstract fun bodyDataDao(): BodyDataDao
}

View File

@ -0,0 +1,19 @@
package de.polyfish0.fitnessjourney.database
import android.content.Context
import androidx.room.Room
object InstanceManager {
lateinit var fitnessDatabase: FitnessDatabase
fun getFitnessDB(context: Context): FitnessDatabase {
if(!this::fitnessDatabase.isInitialized)
fitnessDatabase = Room.databaseBuilder(
context,
FitnessDatabase::class.java,
"fitness-data"
).build()
return fitnessDatabase
}
}

View File

@ -0,0 +1,16 @@
package de.polyfish0.fitnessjourney.database.converter
import androidx.room.TypeConverter
import java.time.LocalDateTime
class LocalDateTimeConverter {
@TypeConverter
fun fromTimestamp(value: String?): LocalDateTime? {
return value?.let { LocalDateTime.parse(it) }
}
@TypeConverter
fun dateToTimestamp(date: LocalDateTime?): String? {
return date?.toString()
}
}

View File

@ -0,0 +1,23 @@
package de.polyfish0.fitnessjourney.database.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import de.polyfish0.fitnessjourney.database.entities.BodyData
import de.polyfish0.fitnessjourney.database.entities.Pictures
@Dao
interface BodyDataDao {
@Query("SELECT * FROM BodyData")
suspend fun getAll(): List<BodyData>
@Query("SELECT * FROM BodyData WHERE pid IN (:pictureIds)")
suspend fun loadAllByIds(pictureIds: IntArray): List<BodyData>
@Insert
suspend fun insertAll(vararg pictures: BodyData)
@Delete
suspend fun delete(picture: BodyData)
}

View File

@ -0,0 +1,22 @@
package de.polyfish0.fitnessjourney.database.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import de.polyfish0.fitnessjourney.database.entities.Pictures
@Dao
interface PicturesDAO {
@Query("SELECT * FROM Pictures")
suspend fun getAll(): List<Pictures>
@Query("SELECT * FROM Pictures WHERE pid IN (:pictureIds)")
suspend fun loadAllByIds(pictureIds: IntArray): List<Pictures>
@Insert
suspend fun insertAll(vararg pictures: Pictures)
@Delete
suspend fun delete(picture: Pictures)
}

View File

@ -0,0 +1,34 @@
package de.polyfish0.fitnessjourney.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import de.polyfish0.fitnessjourney.database.converter.LocalDateTimeConverter
import java.time.LocalDateTime
@Entity(
foreignKeys = [ForeignKey(
entity = Pictures::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("pid"),
onDelete = ForeignKey.SET_NULL
), ForeignKey(
entity = UserData::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("uid"),
onDelete = ForeignKey.SET_NULL
)]
)
@TypeConverters(LocalDateTimeConverter::class)
data class BodyData(
@PrimaryKey(autoGenerate = true) val bdid: Int = 0,
@ColumnInfo(name = "pid") val pid: Int,
@ColumnInfo(name = "uid") val uid: Int,
@ColumnInfo(name = "weight") val weight: Float,
@ColumnInfo(name = "size") val size: Int,
@ColumnInfo(name = "kfa") val kfa: Float,
@ColumnInfo(name = "date")
val date: LocalDateTime = LocalDateTime.now()
)

View File

@ -0,0 +1,18 @@
package de.polyfish0.fitnessjourney.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import de.polyfish0.fitnessjourney.database.converter.LocalDateTimeConverter
import java.time.LocalDateTime
@Entity
@TypeConverters(LocalDateTimeConverter::class)
data class Pictures(
@PrimaryKey(autoGenerate = true) val pid: Int = 0,
@ColumnInfo(name = "path") val path: String,
@ColumnInfo(name = "date")
val date: LocalDateTime = LocalDateTime.now()
)

View File

@ -0,0 +1,13 @@
package de.polyfish0.fitnessjourney.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class UserData(
@PrimaryKey(autoGenerate = true) val uid: Int = 0,
@ColumnInfo(name = "pid") val pid: Int,
@ColumnInfo(name = "size") val size: Int,
@ColumnInfo(name = "male") val male: Boolean
)

View File

@ -3,6 +3,7 @@ package de.polyfish0.fitnessjourney.routes
object Routes { object Routes {
const val MAIN = "Main" const val MAIN = "Main"
const val BODY_COMPOSITION_MAIN = "BodyCompositionMain" const val BODY_COMPOSITION_MAIN = "BodyCompositionMain"
const val ADDING_BODY_COMPOSITION = "AddingBodyComposition"
const val TRAINING_PLANS_MAIN = "TrainingPlansMain" const val TRAINING_PLANS_MAIN = "TrainingPlansMain"
const val SETTINGS = "Settings" const val SETTINGS = "Settings"
} }

View File

@ -0,0 +1,60 @@
package de.polyfish0.fitnessjourney.routes.bodycomposition
import android.content.res.Configuration
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.ViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import de.polyfish0.fitnessjourney.R
import de.polyfish0.fitnessjourney.ui.theme.FitnessJourneyTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@Composable
fun AddBodyCompositionRoute(navigation: NavController) {
Scaffold (bottomBar = {
Button(modifier = Modifier.fillMaxWidth(), onClick = { Log.d("LOL", "Rent") }) {
Text(text = stringResource(id = R.string.save))
}
}) { padding ->
Surface(modifier = Modifier.padding(padding)) {
LazyColumn {
items(5) { index ->
Text(text = "$index")
}
}
}
}
}
class AddBodyCompositionViewModel : ViewModel() {
private val _isLoading = MutableStateFlow(true)
val isLoading: StateFlow<Boolean> get() = _isLoading
fun checkUserInput() {
}
}
@Composable
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
fun AddBodyCompositionRoutePreview() {
FitnessJourneyTheme {
AddBodyCompositionRoute(navigation = rememberNavController())
}
}

View File

@ -1,37 +1,109 @@
package de.polyfish0.fitnessjourney.routes.bodycomposition package de.polyfish0.fitnessjourney.routes.bodycomposition
import android.content.Context
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import de.polyfish0.fitnessjourney.R import de.polyfish0.fitnessjourney.R
import de.polyfish0.fitnessjourney.database.InstanceManager
import de.polyfish0.fitnessjourney.database.entities.Pictures
import de.polyfish0.fitnessjourney.routes.Routes import de.polyfish0.fitnessjourney.routes.Routes
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@Composable @Composable
fun BodyCompositionMainRoute(navigation: NavController) { fun BodyCompositionMainRoute(navigation: NavController) {
val viewModel = viewModel { BodyCompositionDataViewModel() }
val dataState by viewModel.dataFlow.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
if (isLoading) {
val context = LocalContext.current
LaunchedEffect(1) {
viewModel.loadData(context = context)
}
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Card(modifier = Modifier.fillMaxWidth()) { CircularProgressIndicator()
}
return
}
Scaffold(modifier = Modifier.fillMaxSize(), floatingActionButton = {
FloatingActionButton(onClick = { navigation.navigate(Routes.ADDING_BODY_COMPOSITION) }) {
Icon(
Icons.Rounded.Add,
contentDescription = stringResource(id = R.string.add_body_comp)
)
}
}) { padding ->
LazyColumn(modifier = Modifier.padding(padding)) {
itemsIndexed(dataState) { _, item ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
.clickable { Log.d("Yeet", item.path) }) {
Column(modifier = Modifier.padding(4.dp)) { Column(modifier = Modifier.padding(4.dp)) {
Text(text = "Hier gibts noch nichts aber hier sollen krasse Fitness Daten und Statistiken angezeigt werden c:") Text(text = item.path)
Button(onClick = {
navigation.navigate(Routes.MAIN)
}) {
Text(text = stringResource(R.string.app_name))
} }
} }
} }
} }
} }
}
class BodyCompositionDataViewModel : ViewModel() {
private val _isLoading = MutableStateFlow(true)
val isLoading: StateFlow<Boolean> get() = _isLoading
private val _dataFlow = MutableStateFlow<ArrayList<Pictures>>(arrayListOf())
val dataFlow: StateFlow<ArrayList<Pictures>> = _dataFlow
fun loadData(context: Context) {
viewModelScope.launch {
_dataFlow.value.clear()
_dataFlow.value.addAll(InstanceManager.getFitnessDB(context).picturesDao().getAll())
_isLoading.value = false
}
}
fun insertData(context: Context, vararg data: Pictures) {
viewModelScope.launch {
InstanceManager.getFitnessDB(context).picturesDao().insertAll(*data)
}
}
}

View File

@ -4,4 +4,6 @@
<string name="training_plans">Trainingspläne</string> <string name="training_plans">Trainingspläne</string>
<string name="body_composition">Körperdaten</string> <string name="body_composition">Körperdaten</string>
<string name="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
<string name="add_body_comp">Einfügen einer neuen Messung</string>
<string name="save">Speichern</string>
</resources> </resources>

View File

@ -3,4 +3,6 @@
<string name="training_plans">Training plans</string> <string name="training_plans">Training plans</string>
<string name="body_composition">Body composition</string> <string name="body_composition">Body composition</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="add_body_comp">Add a new measurement</string>
<string name="save">Save</string>
</resources> </resources>