Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca36c17011 | |||
| a35e7992b2 | |||
| 972823c5b8 | |||
| bdc3a50a53 | |||
| 6092c8f5e3 | |||
| c236507045 | |||
| 50fba5f697 | |||
| 70acdc756a | |||
| f76ec41891 | |||
| 869d6881b4 | |||
| 45085cdba4 | |||
| c6dd2c633b | |||
| b22c82de00 | |||
| 8350ef65b5 | |||
| a1d0e46bfe | |||
| 5c653f5dce | |||
| f45f1cc77b | |||
| 004824636d | |||
| d432adc142 | |||
| 2c7819f524 | |||
| 1203b5735a | |||
| 5b9d2b756b |
29 changed files with 1270 additions and 106 deletions
158
DBTApp/.idea/codeStyles/Project.xml
generated
Normal file
158
DBTApp/.idea/codeStyles/Project.xml
generated
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" module="true" />
|
||||
<package name="android" withSubpackages="true" static="true" />
|
||||
<package name="androidx" withSubpackages="true" static="true" />
|
||||
<package name="com" withSubpackages="true" static="true" />
|
||||
<package name="junit" withSubpackages="true" static="true" />
|
||||
<package name="net" withSubpackages="true" static="true" />
|
||||
<package name="org" withSubpackages="true" static="true" />
|
||||
<package name="java" withSubpackages="true" static="true" />
|
||||
<package name="javax" withSubpackages="true" static="true" />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="androidx" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
DBTApp/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
DBTApp/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
20
DBTApp/.idea/deploymentTargetSelector.xml
generated
20
DBTApp/.idea/deploymentTargetSelector.xml
generated
|
|
@ -4,22 +4,18 @@
|
|||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="BottomNavigationBar">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-06-06T15:42:28.217017051Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/clara/.android/avd/Pixel_6.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="MainContent">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="SingleSkillCard">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="SingleSkillsCardPreview">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="SkillsScreen">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -2,6 +2,7 @@ plugins {
|
|||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -37,6 +38,9 @@ android {
|
|||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -70,6 +74,12 @@ dependencies {
|
|||
//Compose navigation
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
|
||||
//room (for AppDatabase)
|
||||
implementation(libs.room.runtime)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.ktx)
|
||||
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
|
|
|
|||
43
DBTApp/app/schemas/de.cdaut.dbtapp.model.AppDatabase/1.json
Normal file
43
DBTApp/app/schemas/de.cdaut.dbtapp.model.AppDatabase/1.json
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "9293dc46eca03d297eae6183174db499",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Skill",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identifier` BLOB NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`identifier`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "identifier",
|
||||
"columnName": "identifier",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"identifier"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"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, '9293dc46eca03d297eae6183174db499')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,30 @@
|
|||
package de.cdaut.dbtapp
|
||||
|
||||
import android.graphics.drawable.shapes.Shape
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Emergency
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Shapes
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import de.cdaut.dbtapp.components.BottomNavigationBar
|
||||
import de.cdaut.dbtapp.components.FavouritesScreen
|
||||
import de.cdaut.dbtapp.components.SkillsChainsScreen
|
||||
import de.cdaut.dbtapp.components.SkillsScreen
|
||||
import de.cdaut.dbtapp.components.TrackingScreen
|
||||
import de.cdaut.dbtapp.navigation.Screens
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
|
@ -42,49 +36,52 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(locale = "en-US")
|
||||
@Preview(locale = "en-US", device = "id:pixel_6")
|
||||
@Composable
|
||||
private fun MainContent() {
|
||||
val navController = rememberNavController()
|
||||
Box {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomBar = { BottomNavigationBar(navController) },
|
||||
floatingActionButton = { EmergencyButton() }
|
||||
) { paddingValues ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screens.Home.route,
|
||||
modifier = Modifier.padding(paddingValues = paddingValues)
|
||||
) {
|
||||
composable(Screens.Tracking.route) {
|
||||
Text("Tracking")
|
||||
}
|
||||
composable(Screens.Skillslist.route) {
|
||||
SkillsScreen()
|
||||
}
|
||||
composable(Screens.Home.route) {
|
||||
Text("Home")
|
||||
}
|
||||
composable(Screens.Favourites.route) {
|
||||
Text("Favourites")
|
||||
MaterialTheme {
|
||||
Box {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomBar = { BottomNavigationBar(navController) },
|
||||
floatingActionButton = { EmergencyButton() }
|
||||
) { paddingValues ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screens.Home.route,
|
||||
modifier = Modifier.padding(paddingValues = paddingValues)
|
||||
) {
|
||||
composable(Screens.Tracking.route) {
|
||||
TrackingScreen()
|
||||
}
|
||||
composable(Screens.Skillslist.route) {
|
||||
SkillsScreen()
|
||||
}
|
||||
composable(Screens.Home.route) {
|
||||
Text("Home")
|
||||
}
|
||||
composable(Screens.Favourites.route) {
|
||||
FavouritesScreen()
|
||||
|
||||
}
|
||||
composable(Screens.Skillschains.route) {
|
||||
Text("Skillschains")
|
||||
}
|
||||
composable(Screens.Skillschains.route) {
|
||||
SkillsChainsScreen()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun EmergencyButton() {
|
||||
FloatingActionButton(
|
||||
onClick = {},
|
||||
containerColor = Color.Red
|
||||
onClick = {
|
||||
//TODO: Implement function for emergency Button
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.error
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Emergency,
|
||||
|
|
|
|||
|
|
@ -19,17 +19,19 @@ import de.cdaut.dbtapp.navigation.BottomNavigationItem
|
|||
fun BottomNavigationBar(navController: NavController) {
|
||||
var context = LocalContext.current
|
||||
//remember the currently selected view
|
||||
//default view is home
|
||||
val homeView = BottomNavigationItem()
|
||||
.bottomNavigationItems()
|
||||
.find { item -> item.label == "Home" }!!.idx
|
||||
var navigationSelectedItem by remember {
|
||||
mutableIntStateOf(
|
||||
//default view is home
|
||||
BottomNavigationItem().bottomNavigationItems(ctx = context)
|
||||
.find { item -> item.label == "Home" }!!.idx
|
||||
homeView
|
||||
)
|
||||
}
|
||||
|
||||
NavigationBar {
|
||||
//Create an entry in the bottom bar for each view
|
||||
BottomNavigationItem().bottomNavigationItems(ctx = context).sortedBy { item -> item.idx }
|
||||
BottomNavigationItem().bottomNavigationItems().sortedBy { item -> item.idx }
|
||||
.forEachIndexed { index, item ->
|
||||
NavigationBarItem(
|
||||
selected = index == navigationSelectedItem,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
package de.cdaut.dbtapp.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.cdaut.dbtapp.R
|
||||
import de.cdaut.dbtapp.model.Skill
|
||||
import de.cdaut.dbtapp.model.SkillCategory
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun FavouritesScreen() {
|
||||
Column(
|
||||
Modifier.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
RoundedSearchBar(onSearch = {}, stringResource(R.string.label_skills_search_bar))
|
||||
SkillCategory.entries.forEach { skillCategory ->
|
||||
FavouritesCategoryCard(skillCategory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FavouritesCategoryCard(category: SkillCategory) {
|
||||
var enabled by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Card(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { enabled = !enabled }),
|
||||
) {
|
||||
TitleText(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
content = category.title
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(5.dp),
|
||||
imageVector = if (enabled) Icons.Filled.ArrowDropUp else Icons.Filled.ArrowDropDown,
|
||||
contentDescription = stringResource(R.string.desc_btn_expand)
|
||||
)
|
||||
}
|
||||
if (enabled) {
|
||||
category.skills.forEach { skill ->
|
||||
SingleSkillCard(skill = skill)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleSkillCard(skill: Skill) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.background(Color.Transparent)
|
||||
.padding(10.dp)
|
||||
.background(Color.White, shape = RoundedCornerShape(5.dp))
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(fraction = 0.8f),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
|
||||
) {
|
||||
TitleText(skill.title)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
DescriptionText(skill.description)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(50.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
shape = CircleShape,
|
||||
onClick = {
|
||||
//TODO: Start Skill
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = "TODO: PROVIDE"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package de.cdaut.dbtapp.components
|
||||
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import de.cdaut.dbtapp.R
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RoundedSearchBarPrev() {
|
||||
RoundedSearchBar(onSearch = {}, stringResource(R.string.label_skills_search_bar))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoundedSearchBar(onSearch: (String) -> Unit = {}, placeholder: String) {
|
||||
// Controls expansion state of the search bar
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
var textFieldState = rememberTextFieldState()
|
||||
|
||||
SearchBar(
|
||||
inputField = {
|
||||
SearchBarDefaults.InputField(
|
||||
query = textFieldState.text.toString(),
|
||||
onQueryChange = { textFieldState.edit { replace(0, length, it) } },
|
||||
onSearch = {
|
||||
onSearch(textFieldState.text.toString())
|
||||
expanded = false
|
||||
},
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Filled.Search,
|
||||
contentDescription = stringResource(R.string.content_desc_search_bar)
|
||||
)
|
||||
},
|
||||
placeholder = { Text(placeholder) }
|
||||
)
|
||||
},
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it }
|
||||
) {
|
||||
Text("Placeholder")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package de.cdaut.dbtapp.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.ScrollableState
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.interaction.Interaction
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -15,19 +12,17 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.outlined.Star
|
||||
import androidx.compose.material.icons.outlined.StarOutline
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -40,7 +35,6 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import de.cdaut.dbtapp.R
|
||||
import de.cdaut.dbtapp.model.Skill
|
||||
import de.cdaut.dbtapp.model.SkillCategory
|
||||
|
|
@ -49,8 +43,10 @@ import de.cdaut.dbtapp.model.SkillCategory
|
|||
@Composable
|
||||
fun SkillsScreen() {
|
||||
Column(
|
||||
Modifier.verticalScroll(rememberScrollState())
|
||||
Modifier.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
RoundedSearchBar(onSearch = {}, stringResource(R.string.label_skills_search_bar))
|
||||
SkillCategory.entries.forEach { skillCategory ->
|
||||
SkillsCategoryCard(skillCategory.skills, skillCategory.title)
|
||||
Spacer(Modifier.height(5.dp))
|
||||
|
|
@ -58,7 +54,6 @@ fun SkillsScreen() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SkillsCategoryCardPrev() {
|
||||
SkillsCategoryCard(Skill.mockSkills(), "Skill")
|
||||
|
|
@ -66,22 +61,41 @@ private fun SkillsCategoryCardPrev() {
|
|||
|
||||
@Composable
|
||||
fun SkillsCategoryCard(skills: List<Skill>, title: String) {
|
||||
var enabled by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Card(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
) {
|
||||
TitleText(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
content = title
|
||||
)
|
||||
skills.forEach { skill ->
|
||||
SingleSkillCard(skill.title, skill.description)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { enabled = !enabled }),
|
||||
) {
|
||||
TitleText(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
content = title
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(5.dp),
|
||||
imageVector = if (enabled) Icons.Filled.ArrowDropUp else Icons.Filled.ArrowDropDown,
|
||||
contentDescription = stringResource(R.string.desc_btn_expand)
|
||||
)
|
||||
}
|
||||
if (enabled) {
|
||||
skills.forEach { skill ->
|
||||
SingleSkillCard(skill.title, skill.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SingleSkillsCardPreview() {
|
||||
SingleSkillCard(
|
||||
|
|
@ -118,16 +132,29 @@ private fun SingleSkillCard(title: String, description: String) {
|
|||
DescriptionText(description)
|
||||
}
|
||||
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.clickable(onClick = {
|
||||
selected = !selected
|
||||
}),
|
||||
imageVector = if (selected) Icons.Outlined.Star else Icons.Outlined.StarOutline,
|
||||
contentDescription = stringResource(R.string.desc_btn_fav),
|
||||
tint = if (selected) Color.Yellow else Color.Black
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.clip(CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (selected) {
|
||||
Icon(
|
||||
modifier = Modifier.clip(CircleShape),
|
||||
imageVector = Icons.Filled.Star,
|
||||
contentDescription = "Filled Star",
|
||||
tint = Color.Yellow
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(10.dp)
|
||||
.clickable(onClick = {
|
||||
selected = !selected
|
||||
}),
|
||||
imageVector = Icons.Outlined.StarOutline,
|
||||
contentDescription = stringResource(R.string.desc_btn_fav)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package de.cdaut.dbtapp.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.cdaut.dbtapp.R
|
||||
import de.cdaut.dbtapp.model.Skill
|
||||
import de.cdaut.dbtapp.model.Skillschain
|
||||
import de.cdaut.dbtapp.model.Skillschain.Companion.mockSkillschains
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SkillsChainsScreen() {
|
||||
Column(
|
||||
Modifier.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
RoundedSearchBar(onSearch = {}, stringResource(R.string.label_skillschains_search_bar))
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
AddSkillschainBtn()
|
||||
|
||||
mockSkillschains().forEach {
|
||||
SkillschainCard(skillschain = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddSkillschainBtn(modifier: Modifier = Modifier) {
|
||||
Button(
|
||||
onClick = {
|
||||
//TODO: Add Skillschain
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.9f)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "//TODO: Provide"
|
||||
)
|
||||
Text(stringResource(R.string.btn_add_skillschain))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SkillschainCard(modifier: Modifier = Modifier, skillschain: Skillschain) {
|
||||
|
||||
var open by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Card(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
.background(MaterialTheme.colorScheme.primaryContainer, CircleShape)
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
TitleText(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
content = skillschain.title
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(50.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
shape = CircleShape,
|
||||
onClick = {
|
||||
//TODO: Start Skills Chain
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = "TODO: PROVIDE"
|
||||
)
|
||||
}
|
||||
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(5.dp)
|
||||
.clickable(onClick = { open = !open }),
|
||||
imageVector = if (open) Icons.Filled.ArrowDropUp else Icons.Filled.ArrowDropDown,
|
||||
contentDescription = stringResource(R.string.desc_btn_expand)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (open) {
|
||||
skillschain.skills.forEach {
|
||||
SkillCard(skill = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SkillCard(modifier: Modifier = Modifier, skill: Skill) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Transparent)
|
||||
.padding(10.dp)
|
||||
.background(Color.White, shape = RoundedCornerShape(5.dp))
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
|
||||
) {
|
||||
TitleText(skill.title)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
DescriptionText(skill.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,8 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Preview
|
||||
|
|
|
|||
400
DBTApp/app/src/main/java/de/cdaut/dbtapp/components/Tracking.kt
Normal file
400
DBTApp/app/src/main/java/de/cdaut/dbtapp/components/Tracking.kt
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
package de.cdaut.dbtapp.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.CalendarMonth
|
||||
import androidx.compose.material.icons.filled.ChevronLeft
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material.icons.filled.RemoveRedEye
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import de.cdaut.dbtapp.R
|
||||
import de.cdaut.dbtapp.model.TherapyAssignment
|
||||
import de.cdaut.dbtapp.model.TherapyAssignment.Companion.mockTherapyAssignments
|
||||
import de.cdaut.dbtapp.model.Tracker
|
||||
import de.cdaut.dbtapp.model.Tracker.Companion.mockTrackers
|
||||
import de.cdaut.dbtapp.util.weekdayListByLocale
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.time.temporal.WeekFields
|
||||
|
||||
@Preview(device = "id:pixel_6")
|
||||
@Composable
|
||||
fun TrackingScreen() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.9f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Card {
|
||||
TopCalendar()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
//TODO: Add tracker
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Add,
|
||||
contentDescription = "//TODO: Provide",
|
||||
tint = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.add_trackter),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
//TODO: View statistics
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 5.dp),
|
||||
text = stringResource(R.string.view_stats),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
Icon(
|
||||
Icons.Filled.RemoveRedEye,
|
||||
contentDescription = "//TODO: Provide",
|
||||
tint = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
//TODO: Add diary card here
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Icon(Icons.Filled.Add, stringResource(R.string.btn_add_diarycard))
|
||||
Text(stringResource(R.string.btn_add_diarycard))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
TitledListCard<TherapyAssignment>(
|
||||
stringResource(R.string.card_therapy_hw_heading),
|
||||
//cannot eta reduce because
|
||||
//Function References of @Composable functions are not currently supported
|
||||
rows = { assignments ->
|
||||
TherapyAssignmentCardContent(assignments)
|
||||
},
|
||||
addAction = {
|
||||
//TODO: Add Therapy assignment
|
||||
},
|
||||
items = mockTherapyAssignments()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
TitledListCard<Tracker>(
|
||||
stringResource(R.string.tracker_card_title),
|
||||
//cannot eta reduce because
|
||||
//Function References of @Composable functions are not currently supported
|
||||
rows = { trackers -> OtherTrackersCardContent(trackers = List(4) { _ -> trackers[0] }) },
|
||||
addAction = {
|
||||
//TODO: Add new tracker
|
||||
},
|
||||
items = mockTrackers()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopCalendar() {
|
||||
|
||||
var currentDateMut: MutableState<LocalDate> = remember {
|
||||
mutableStateOf(LocalDate.now())
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
//Top Navigation and display items
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
//Header stating the currently selected date
|
||||
Text(
|
||||
fontSize = 4.5.em,
|
||||
text = currentDateMut.value.format(
|
||||
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
|
||||
.withLocale(LocalConfiguration.current.locales[0])
|
||||
)
|
||||
)
|
||||
// arrows to switch month
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = {
|
||||
currentDateMut.value = LocalDate.now()
|
||||
}),
|
||||
imageVector = Icons.Filled.CalendarMonth,
|
||||
contentDescription = "//TODO: Provide"
|
||||
)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = {
|
||||
currentDateMut.value = currentDateMut.value.minusMonths(1)
|
||||
})
|
||||
.size(30.dp),
|
||||
imageVector = Icons.Filled.ChevronLeft,
|
||||
contentDescription = "TODO: Provide"
|
||||
)
|
||||
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = {
|
||||
currentDateMut.value = currentDateMut.value.plusMonths(1)
|
||||
})
|
||||
.size(30.dp),
|
||||
imageVector = Icons.Filled.ChevronRight,
|
||||
contentDescription = "TODO: Provide"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// labels for current day
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
weekdayListByLocale(LocalConfiguration.current.locales[0]).map { label ->
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier.padding(10.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CalendarDaysGrid(currentDateMut)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CalendarDaysGrid(selectedDateMut: MutableState<LocalDate>) {
|
||||
val locale = LocalConfiguration.current.locales[0]
|
||||
val selectedDate = selectedDateMut.value
|
||||
|
||||
val firstDayOfMonth = LocalDate.of(selectedDate.year, selectedDate.month, 1)
|
||||
val totalDaysInMonth = firstDayOfMonth.lengthOfMonth()
|
||||
val firstDayOfWeek = WeekFields.of(locale).firstDayOfWeek
|
||||
|
||||
//calculate shift required in the beginning
|
||||
val shift = (firstDayOfMonth.dayOfWeek.value - firstDayOfWeek.value + 7) % 7
|
||||
val totalCells = ((shift + totalDaysInMonth + 6) / 7) * 7
|
||||
|
||||
val days = List(totalCells) { index ->
|
||||
val dayNumber = index - shift + 1
|
||||
if (dayNumber in 1..totalDaysInMonth) dayNumber else null
|
||||
}
|
||||
|
||||
// Calendar Grid
|
||||
// FIXME: Something better than a lazy grid would be good because scrolling
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(7),
|
||||
userScrollEnabled = false,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(320.dp)
|
||||
) {
|
||||
items(days.size) { index ->
|
||||
days[index]?.let { day ->
|
||||
|
||||
var dayBoxModifier = Modifier
|
||||
.padding(4.dp)
|
||||
.clip(CircleShape)
|
||||
.aspectRatio(1f)
|
||||
.clickable(onClick = {
|
||||
selectedDateMut.value = LocalDate.of(
|
||||
selectedDate.year,
|
||||
selectedDate.month,
|
||||
day
|
||||
)
|
||||
})
|
||||
.clip(CircleShape)
|
||||
|
||||
//add a mark to the currently selected day
|
||||
if (day == selectedDate.dayOfMonth) {
|
||||
dayBoxModifier = dayBoxModifier.border(
|
||||
2.dp,
|
||||
MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
CircleShape
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = dayBoxModifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = day.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun <T> TitledListCard(
|
||||
title: String,
|
||||
rows: @Composable Function1<List<T>, Unit>,
|
||||
addAction: () -> Unit,
|
||||
items: List<T>
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = addAction),
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "TODO: Provide"
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
rows(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TherapyAssignmentCardContent(assignments: List<TherapyAssignment>) {
|
||||
for (assignment in assignments) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 0.dp),
|
||||
text = assignment.title,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 0.dp),
|
||||
text = assignment.description,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
Checkbox(
|
||||
onCheckedChange = {
|
||||
assignment.done = !assignment.done
|
||||
},
|
||||
checked = assignment.done
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OtherTrackersCardContent(trackers: List<Tracker>) {
|
||||
for (tracker in trackers) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 0.dp),
|
||||
text = tracker.title,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 0.dp),
|
||||
text = tracker.description,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package de.cdaut.dbtapp.model
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(entities = [Skill::class], version = 1)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun skillDao() : SkillDao
|
||||
}
|
||||
|
|
@ -1,24 +1,53 @@
|
|||
package de.cdaut.dbtapp.model
|
||||
|
||||
class Skill(val title: String, val description: String) {
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Insert
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Query
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
class Skill(
|
||||
@PrimaryKey val identifier: UUID = UUID.randomUUID(),
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "description") val description: String
|
||||
) {
|
||||
|
||||
|
||||
companion object {
|
||||
fun mockSkills(): List<Skill> {
|
||||
return listOf(
|
||||
Skill(
|
||||
title = "Test Hallo :3",
|
||||
description = "lorem ipsum dolor sid amnet consequetur blabla yada yada"
|
||||
title = "5-4-3-2-1",
|
||||
description = "5 sehen, 5 hören, 5 fühlen, 4 sehen, 4 hören…"
|
||||
),
|
||||
Skill(
|
||||
title = "5-4-3-2-1",
|
||||
title = "Skilltitel",
|
||||
description = "Hier kurz beschreiben wie die Übung funktioniert. Ggf. mehrere Zeilen aber nicht super lang"
|
||||
),
|
||||
Skill(
|
||||
title = "UwU UwU awawawa",
|
||||
description = "Just arf a little like the good fopsgirl you are :3"
|
||||
)
|
||||
// Skill(
|
||||
// title = "UwU UwU awawawa",
|
||||
// description = "Just arf a little like the good fopsgirl you are :3"
|
||||
// )
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface SkillDao {
|
||||
@Query("SELECT * FROM Skill")
|
||||
fun getAll(): List<Skill>
|
||||
|
||||
@Query("SELECT * FROM Skill WHERE title LIKE :title LIMIT 1")
|
||||
fun findByName(title: String): Skill
|
||||
|
||||
@Insert
|
||||
fun insertAll(vararg skills: Skill)
|
||||
|
||||
@Delete
|
||||
fun delete(skill: Skill)
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package de.cdaut.dbtapp.model
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
||||
enum class SkillCategory(val title: String, val skills: List<Skill> = Skill.mockSkills()) {
|
||||
Mindfulness("Achtsamkeitsübungen"),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package de.cdaut.dbtapp.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
class Skillschain(
|
||||
@PrimaryKey val identifier: UUID = UUID.randomUUID(),
|
||||
@ColumnInfo(name = "name") val title: String,
|
||||
@ColumnInfo(name = "skills") val skills: List<Skill>
|
||||
) {
|
||||
companion object {
|
||||
fun mockSkillschains(): List<Skillschain> {
|
||||
return listOf(
|
||||
Skillschain(
|
||||
title = "Skillskette 1",
|
||||
skills = Skill.mockSkills()
|
||||
),
|
||||
Skillschain(
|
||||
title = "Skillskette 2",
|
||||
skills = Skill.mockSkills()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package de.cdaut.dbtapp.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
class TherapyAssignment(
|
||||
@PrimaryKey val identifier: UUID = UUID.randomUUID(),
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "description") val description: String,
|
||||
@ColumnInfo(name = "done") var done: Boolean
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun mockTherapyAssignments(): List<TherapyAssignment> {
|
||||
return listOf(
|
||||
TherapyAssignment(
|
||||
title = "Selbstfürsorge",
|
||||
description = "Mir selbst eine gute Sache tun",
|
||||
done = true
|
||||
),
|
||||
TherapyAssignment(
|
||||
title = "Bedürfnis durchsetzen",
|
||||
description = "Eine Sache durchsetzen, die mir schwer fällt",
|
||||
done = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
26
DBTApp/app/src/main/java/de/cdaut/dbtapp/model/Tracker.kt
Normal file
26
DBTApp/app/src/main/java/de/cdaut/dbtapp/model/Tracker.kt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package de.cdaut.dbtapp.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
class Tracker(
|
||||
@PrimaryKey val identifier: UUID = UUID.randomUUID(),
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "description") val description: String
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun mockTrackers(): List<Tracker> {
|
||||
return listOf(
|
||||
Tracker(
|
||||
title = "Stimmung",
|
||||
description = "Deine aktuelle Stimmung"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
package de.cdaut.dbtapp.navigation
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||
import androidx.compose.material.icons.filled.CalendarToday
|
||||
import androidx.compose.material.icons.filled.Checklist
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.outlined.Star
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat.getString
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import de.cdaut.dbtapp.R
|
||||
|
||||
data class BottomNavigationItem(
|
||||
|
|
@ -18,34 +17,35 @@ data class BottomNavigationItem(
|
|||
val route: String = "",
|
||||
val idx: Int = 0
|
||||
) {
|
||||
fun bottomNavigationItems(ctx: Context): List<BottomNavigationItem> {
|
||||
@Composable
|
||||
fun bottomNavigationItems(): List<BottomNavigationItem> {
|
||||
return listOf(
|
||||
BottomNavigationItem(
|
||||
label = getString(ctx, R.string.label_tracking),
|
||||
label = stringResource(R.string.label_tracking),
|
||||
icon = Icons.Filled.CalendarToday,
|
||||
route = Screens.Tracking.route,
|
||||
idx = 0
|
||||
),
|
||||
BottomNavigationItem(
|
||||
label = getString(ctx, R.string.label_skillslist),
|
||||
label = stringResource(R.string.label_skillslist),
|
||||
icon = Icons.AutoMirrored.Filled.MenuBook,
|
||||
route = Screens.Skillslist.route,
|
||||
idx = 1
|
||||
),
|
||||
BottomNavigationItem(
|
||||
label = getString(ctx, R.string.label_home),
|
||||
label = stringResource(R.string.label_home),
|
||||
icon = Icons.Filled.Home,
|
||||
route = Screens.Home.route,
|
||||
idx = 2
|
||||
),
|
||||
BottomNavigationItem(
|
||||
label = getString(ctx, R.string.label_favourites),
|
||||
label = stringResource(R.string.label_favourites),
|
||||
icon = Icons.Outlined.Star,
|
||||
route = Screens.Favourites.route,
|
||||
idx = 3
|
||||
),
|
||||
BottomNavigationItem(
|
||||
label = getString(ctx, R.string.label_skillschains),
|
||||
label = stringResource(R.string.label_skillschains),
|
||||
icon = Icons.Filled.Checklist,
|
||||
route = Screens.Skillschains.route,
|
||||
idx = 4
|
||||
|
|
|
|||
15
DBTApp/app/src/main/java/de/cdaut/dbtapp/util/DateHelpers.kt
Normal file
15
DBTApp/app/src/main/java/de/cdaut/dbtapp/util/DateHelpers.kt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package de.cdaut.dbtapp.util
|
||||
|
||||
import java.time.DayOfWeek
|
||||
import java.time.format.TextStyle
|
||||
import java.time.temporal.WeekFields
|
||||
import java.util.Locale
|
||||
|
||||
fun weekdayListByLocale(locale: Locale): List<String> {
|
||||
val listOfDayNames = DayOfWeek.entries.map { it.getDisplayName(TextStyle.NARROW, locale).substring(0, 1) }
|
||||
val firstDayOfWeek = WeekFields.of(locale).firstDayOfWeek.ordinal
|
||||
val orderedListOfDayNames =
|
||||
listOfDayNames.subList(firstDayOfWeek, 7) + listOfDayNames.subList(0, firstDayOfWeek)
|
||||
|
||||
return orderedListOfDayNames
|
||||
}
|
||||
|
|
@ -8,4 +8,14 @@
|
|||
<string name="label_skillschains">Skillsketten</string>
|
||||
<string name="emergency_button_description">Notfallknopf</string>
|
||||
<string name="desc_btn_fav">Favorisieren Button</string>
|
||||
<string name="desc_btn_expand">Ausklappen</string>
|
||||
<string name="btn_add_diarycard">Diary Card Hinzufügen</string>
|
||||
<string name="card_therapy_hw_heading">Therapiehausaufgaben</string>
|
||||
<string name="add_trackter">Tracker hinzufügen</string>
|
||||
<string name="view_stats">Statistiken</string>
|
||||
<string name="tracker_card_title">Tracker</string>
|
||||
<string name="label_skills_search_bar">Skill Suchen</string>
|
||||
<string name="content_desc_search_bar">Suche Icon</string>
|
||||
<string name="label_skillschains_search_bar">Skillskette Suchen</string>
|
||||
<string name="btn_add_skillschain">Skillskette hinzufügen</string>
|
||||
</resources>
|
||||
|
|
@ -7,4 +7,14 @@
|
|||
<string name="label_skillschains">Skills Chains</string>
|
||||
<string name="emergency_button_description">Emergency Button</string>
|
||||
<string name="desc_btn_fav">Favourte Button</string>
|
||||
<string name="desc_btn_expand">Expand</string>
|
||||
<string name="btn_add_diarycard">Add Diary Card</string>
|
||||
<string name="card_therapy_hw_heading">Therapy Assignments</string>
|
||||
<string name="add_trackter">Add Tracker</string>
|
||||
<string name="view_stats">View Statistics</string>
|
||||
<string name="tracker_card_title">Trackers</string>
|
||||
<string name="label_skills_search_bar">Search Skill</string>
|
||||
<string name="content_desc_search_bar">Search Icon</string>
|
||||
<string name="label_skillschains_search_bar">Search Skills Chain</string>
|
||||
<string name="btn_add_skillschain">Add Skillschain</string>
|
||||
</resources>
|
||||
|
|
@ -3,4 +3,5 @@ plugins {
|
|||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[versions]
|
||||
activityCompose = "1.10.1"
|
||||
agp = "8.9.2"
|
||||
agp = "8.10.1"
|
||||
kotlin = "2.0.21"
|
||||
coreKtx = "1.16.0"
|
||||
junit = "4.13.2"
|
||||
|
|
@ -8,7 +8,10 @@ junitVersion = "1.2.1"
|
|||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
material = "1.12.0"
|
||||
navigationCompose = "2.7.0-rc01"
|
||||
navigationCompose = "2.9.0"
|
||||
roomCompiler = "2.7.1"
|
||||
roomKtx = "2.7.1"
|
||||
roomRuntime = "2.7.1"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||
|
|
@ -18,6 +21,9 @@ androidx-material-icons-core = { module = "androidx.compose.material:material-ic
|
|||
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
|
||||
androidx-material3 = { module = "androidx.compose.material3:material3" }
|
||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
|
||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
|
||||
androidx-room-runtime = { module = "androidx.room:room-runtime" }
|
||||
androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
||||
androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
||||
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
||||
|
|
@ -27,6 +33,7 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j
|
|||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
|
|
|||
BIN
planning/screenshots/favourites_screen.png
Normal file
BIN
planning/screenshots/favourites_screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 551 KiB |
BIN
planning/screenshots/skillschains_screen.png
Normal file
BIN
planning/screenshots/skillschains_screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 532 KiB |
BIN
planning/screenshots/skillslist.png
Normal file
BIN
planning/screenshots/skillslist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 549 KiB |
BIN
planning/screenshots/tracking_screen.png
Normal file
BIN
planning/screenshots/tracking_screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 544 KiB |
Loading…
Add table
Add a link
Reference in a new issue