diff --git a/docs/laborok/todo_compose_basics/assets/create.png b/docs/laborok/todo_compose_basics/assets/create.png
index 08d2dd2..4d2772e 100644
Binary files a/docs/laborok/todo_compose_basics/assets/create.png and b/docs/laborok/todo_compose_basics/assets/create.png differ
diff --git a/docs/laborok/todo_compose_basics/assets/datepicker.png b/docs/laborok/todo_compose_basics/assets/datepicker.png
index 854e634..eeb1c7b 100644
Binary files a/docs/laborok/todo_compose_basics/assets/datepicker.png and b/docs/laborok/todo_compose_basics/assets/datepicker.png differ
diff --git a/docs/laborok/todo_compose_basics/assets/details.png b/docs/laborok/todo_compose_basics/assets/details.png
index 02f1c06..37fc6ca 100644
Binary files a/docs/laborok/todo_compose_basics/assets/details.png and b/docs/laborok/todo_compose_basics/assets/details.png differ
diff --git a/docs/laborok/todo_compose_basics/assets/list.png b/docs/laborok/todo_compose_basics/assets/list.png
index 1af83fe..ce2ca4d 100644
Binary files a/docs/laborok/todo_compose_basics/assets/list.png and b/docs/laborok/todo_compose_basics/assets/list.png differ
diff --git a/docs/laborok/todo_compose_basics/index.md b/docs/laborok/todo_compose_basics/index.md
index 8656b9c..9fbd377 100644
--- a/docs/laborok/todo_compose_basics/index.md
+++ b/docs/laborok/todo_compose_basics/index.md
@@ -3,10 +3,10 @@
A labor célja, hogy bemutassa, hogyan lehet egy egyszerű ToDo alkalmazást megvalósítani a Compose keretrendszerben.
-
-
-
-
+
+
+
+
## Előkészületek
@@ -41,80 +41,53 @@ Ezután indítsuk el az Android Studio-t, majd:
Ellenőrízzük, hogy a létrejött projekt lefordul és helyesen működik!
-## Szöveges erőforrások definiálása
-
-A `strings.xml` fájl működését már ismerjük, töltsük fel ezt előre a később szükséges szöveges címkékkel, hogy később a lényeges elemekre tudjunk koncentrálni:
-
-```xml
-
- Todo
- Error
- none
- low
- medium
- high
- "You haven\\'t added any todos yet. "
- Your todos
- The due date is: %1$s
- Description
- Title
- Create todo
- OK
- Close
-
-```
-
## Verziók frissítése
-Annak érdekében, hogy mindig kompatibilis compose könyvtárakat importáljunk a projektben, használjuk a [Compose Bill of Materials](https://developer.android.com/jetpack/compose/bom/bom)-t. Ehhez adjuk hozzá a _modul_ szintű `build.gradle` fájlhoz a következőt a dependencies részhez:
-```gradle
- val composeBom = platform("androidx.compose:compose-bom:2024.02.02")
- implementation(composeBom)
- androidTestImplementation(composeBom)
+Vegyük fel a szükséges könyvtárakat a `libs.versions.toml` fájlban:
+
+```toml
+[versions]
+...
+composeBom = "2024.09.03"
+kotlinxDatetime = "0.4.1"
+lifecycleVersion = "2.8.6"
+navigationCompose = "2.8.2"
+
+[libraries]
+...
+androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name="lifecycle-runtime-compose", version.ref = "lifecycleVersion" }
+androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name="lifecycle-viewmodel-compose", version.ref = "lifecycleVersion" }
+androidx-material-icons-extended = { group = "androidx.compose.material", name="material-icons-extended" }
+androidx-navigation-compose = { group = "androidx.navigation", name="navigation-compose", version.ref = "navigationCompose" }
+kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
```
-Majd minden Compose-hoz kapcsolható könyvtár importálásánál töröljük a verziót, a végeredményben ezt kapva:
+
+Majd pedig használjuk is ezeket a modul szintű `build.gradle.kts` fájlban:
```gradle
dependencies {
+ ...
//Compose Bill of Materials
- val composeBom = platform("androidx.compose:compose-bom:2024.02.02")
- implementation(composeBom)
- androidTestImplementation(composeBom)
+ implementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(platform(libs.androidx.compose.bom))
//ViewModel Lifecycle
- val lifecycleVersion = "2.7.0"
- implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycleVersion")
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion")
+ implementation(libs.androidx.lifecycle.runtime.compose)
+ implementation(libs.androidx.lifecycle.viewmodel.compose)
//Kotlin Extensions DateTime - LocalDate
- implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1")
+ implementation(libs.kotlinx.datetime)
//Compose Navigation
- implementation("androidx.navigation:navigation-compose:2.7.7")
+ implementation(libs.androidx.navigation.compose)
//Material Icons
- implementation("androidx.compose.material:material-icons-extended")
-
- implementation("androidx.core:core-ktx:1.12.0")
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
- implementation("androidx.activity:activity-compose")
- implementation("androidx.compose.ui:ui")
- implementation("androidx.compose.ui:ui-graphics")
- implementation("androidx.compose.ui:ui-tooling-preview")
- implementation("androidx.compose.material3:material3")
-
- testImplementation("junit:junit:4.13.2")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
- androidTestImplementation("androidx.compose.ui:ui-test-junit4")
- debugImplementation("androidx.compose.ui:ui-tooling")
- debugImplementation("androidx.compose.ui:ui-test-manifest")
-
- coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
+ implementation(libs.androidx.material.icons.extended)
}
```
+
!!!danger "Függőségek"
Az itt található kódban minden függőség szerepel, a labor során újat hozzáadni nem kell. Azonban az egyértelműség kedvéért a későbbiekben mindenhol feltüntetjük az adott területhez szükséges függőségeket.
@@ -124,34 +97,27 @@ A fenti függőségekhez 34-es SDK-val kell fordítanunk a projektet, ha a legen
compileSdk = 34
```
-Vegyük fel a `compileOptions` részbe a `isCoreLibraryDesugaringEnabled = true` értéket, ezek mellett ellenőrizzük a kotlin plugin és a compose verzióját. A labor készítésekor a következőek voltak érvényben:
+## Szöveges erőforrások definiálása
-- _Projekt_ szintű `build.gradle`:
-```gradle
-plugins {
- ...
- id("org.jetbrains.kotlin.android") version "1.9.0" apply false
-}
-```
-- _Modul_ szintű `build.gradle`:
-```gradle
-android {
- ...
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+A `strings.xml` fájl működését már ismerjük, töltsük fel ezt előre a később szükséges szöveges címkékkel, hogy később a lényeges elemekre tudjunk koncentrálni:
- isCoreLibraryDesugaringEnabled = true
- }
- ...
- composeOptions {
- kotlinCompilerExtensionVersion = "1.5.1"
- }
-}
-dependencies {
- ...
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
-}
+```xml
+
+ Todo
+ Error
+ none
+ low
+ medium
+ high
+ "You haven\\'t added any todos yet. "
+ Your todos
+ The due date is: %1$s
+ Description
+ Title
+ Create todo
+ OK
+ Close
+
```
## Adatosztályok létrehozása
@@ -166,7 +132,7 @@ Mielőtt nekilátnánk az alkalmazás felületeinek, illetve logikájának kiala
Hozzunk létre egy új `domain` package-t létre a projektünk gyökerében, mely az alkalmazásunk adatrétegének részeit fogja tartalmazni, majd ezen belül hozzunk létre egy `model` package-et, mely az adatmodellek osztály megfelelőit fogja tartalmazni. Ebben hozzuk létre az alábbi két fájlt:
`Todo.kt`:
```kotlin
-package hu.bme.aut.android.todo.domain
+package hu.bme.aut.android.todo.domain.model
import kotlinx.datetime.LocalDate
@@ -195,8 +161,12 @@ enum class Priority {
A `LocalDate` egy általános implementációja az idő kezelésének, mely multiplatform környezetben is használható, ehhez a következő függőséget kell hozzáadnunk a _modul_ szintű build.gradle fájlhoz:
- ```gradle
- implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1")
+ ```tml
+ [versions]
+ kotlinxDatetime = "0.4.1"
+
+ [libraries]
+ kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
```
!!!danger "Idő osztályok kezelése"
@@ -245,7 +215,7 @@ package hu.bme.aut.android.todo.ui.model
import androidx.compose.ui.graphics.Color
import hu.bme.aut.android.todo.R
-import hu.bme.aut.android.todo.domain.Priority
+import hu.bme.aut.android.todo.domain.model.Priority
enum class PriorityUi(
val title: Int,
@@ -293,7 +263,7 @@ fun Priority.asPriorityUi(): PriorityUi {
```kotlin
package hu.bme.aut.android.todo.ui.model
-import hu.bme.aut.android.todo.domain.Todo
+import hu.bme.aut.android.todo.domain.model.Todo
import kotlinx.datetime.LocalDate
import kotlinx.datetime.toLocalDate
import java.time.LocalDateTime
@@ -335,7 +305,11 @@ Az előző laborhoz hasonlóan alakítsuk ki a projektben a navigációnál hasz
Itt is a Compose Navigation könyvtárat fogjuk használni, ezért adjuk ezt hozzá a _modul_ szintű build.gradle fájlunkhoz.
```kotlin
- implementation("androidx.navigation:navigation-compose:2.7.7")
+ [versions]
+ navigationCompose = "2.8.1"
+
+ [libraries]
+ androidx-navigation-compose = { group = "androidx.navigation", name="navigation-compose", version.ref = "navigationCompose" }
```
Hozzunk létre a gyökérkönyvtárban egy új package-et `navigation` néven, majd hozzuk létre benne az útvonalakat reprezentáló `Screen` osztályt:
@@ -411,13 +385,15 @@ Az adatok kezeléséhez tipikusan a `ViewModel` osztályt használjuk. A `ViewMo
??? info "ViewModel Lifecycle"
Vegyük fel a szükséges függőségeket:
- ```gradle
- val lifecycle_version = "2.7.0"
- implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version")
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
+ ```toml
+ [versions]
+ lifecycleVersion = "2.8.6"
+ [libraries]
+ androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name="lifecycle-runtime-compose", version.ref = "lifecycleVersion" }
+ androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name="lifecycle-viewmodel-compose", version.ref = "lifecycleVersion" }
```
-Hozzunk létre a gyökérkönyvtáron belül a `feature` package-et, mely az egyes oldalak `Composable` és `ViewModel` osztályait fogja tartalmazni külön packagenként, majd hozzuk létre ebben a `todo_list` package-t.
+Hozzuk létre a gyökérkönyvtáron belül a `feature` package-et, mely az egyes oldalak `Composable` és `ViewModel` osztályait fogja tartalmazni funkciónként külön *package*-ben, majd hozzuk létre ebben a `todo_list` package-t.
Először foglalkozzunk az oldalhoz tartozó `ViewModel` osztállyal. Hozzuk létre a `TodoListViewModel.kt` fájlt, majd másoljuk be az alábbi kódrészletet:
@@ -504,14 +480,22 @@ Hozzuk létre a felületet megvalósító `TodoListScreen.kt` fájlt is ugyanebb
package hu.bme.aut.android.todo.feature.todo_list
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Circle
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LargeFloatingActionButton
+import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -520,10 +504,14 @@ 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.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import hu.bme.aut.android.todo.R
import hu.bme.aut.android.todo.ui.model.toUiText
+import androidx.compose.foundation.lazy.items
+import androidx.compose.ui.tooling.preview.Preview
@Composable
fun TodoListScreen(
@@ -545,11 +533,12 @@ fun TodoListScreen(
Icon(imageVector = Icons.Default.Add, contentDescription = null)
}
}
- ) {
+ ) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
- .padding(it)
+ .padding(innerPadding)
+ .padding(8.dp)
.background(
color = if (state is TodoListState.Loading || state is TodoListState.Error) {
MaterialTheme.colorScheme.secondaryContainer
@@ -563,9 +552,11 @@ fun TodoListScreen(
is TodoListState.Loading -> CircularProgressIndicator(
color = MaterialTheme.colorScheme.secondaryContainer
)
+
is TodoListState.Error -> Text(
text = state.error.toUiText().asString(context)
)
+
is TodoListState.Result -> {
if (state.todoList.isEmpty()) {
Text(text = stringResource(id = R.string.text_empty_todo_list))
@@ -577,9 +568,10 @@ fun TodoListScreen(
}
}
}
+
```
-Mint a legtöbb esetben, itt is egy `Scaffold`-ot használunk az oldalunk kezelésére, melyhez most egy `LargeFloatingActionButton`-t is adunk, mellyel majd új feladatokat lehet létrehozni. Ne felejtsük el a Scaffold fő tartalmában `it` névvel megkapott `PaddingValues` értékeket a megfelelő helyre beszúrni (ez ebben az esetben a fő `Box` köré kerül. Ezek mellett látható, hogyan tudunk az aktuális állapot különböző értékeinek függvényében elágazni, és különböző elemeket megjeleníteni.
+Mint a legtöbb esetben, itt is egy `Scaffold`-ot használunk az oldalunk kezelésére, melyhez most egy `LargeFloatingActionButton`-t is adunk, amellyel majd új feladatokat lehet létrehozni. Ne felejtsük el a Scaffold fő tartalmában `innerPadding` névvel megkapott `PaddingValues` értékeket a megfelelő helyre beszúrni (ez ebben az esetben a fő `Box` köré kerül. Ezek mellett látható, hogyan tudunk az aktuális állapot különböző értékeinek függvényében elágazni, és különböző elemeket megjeleníteni.
Vizsgáljuk meg, hogyan történik az oldal frissítése! A `collectAsStateWithLifecycle()` függvényhívással automatikusan feliratkozunk a `ViewModel`-ben tárolt állapotra. Ha változás történik ebben, újra le fog futni a `Composable`, mely így már a frisebb állapotot fogja megjeleníteni.
@@ -597,22 +589,17 @@ Column {
) {
items(state.todoList, key = { todo -> todo.id }) { todo ->
ListItem(
+ leadingContent = {
+ Icon(
+ imageVector = Icons.Default.Circle,
+ contentDescription = null,
+ tint = todo.priority.color,
+ modifier = Modifier
+ .size(64.dp)
+ )
+ },
headlineContent = {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(
- imageVector = Icons.Default.Circle,
- contentDescription = null,
- tint = todo.priority.color,
- modifier = Modifier
- .size(40.dp)
- .padding(
- end = 8.dp,
- top = 8.dp,
- bottom = 8.dp
- ),
- )
- Text(text = todo.title)
- }
+ Text(text = todo.title)
},
supportingContent = {
Text(
@@ -642,18 +629,22 @@ Column {
??? info "Material Icon"
A `Circle` ikon csak a kiegészítő Material Icon könyvtárban található meg, melyet az alábbi függőséggel tudunk hozzáadni a projekthez:
- ```gradle
- implementation("androidx.compose.material:material-icons-extended")
+ ```toml
+ [libraries]
+ androidx-material-icons-extended = { group = "androidx.compose.material", name="material-icons-extended" }
```
-Ha hibát dobna az `items`-re, és nem találja az importot, adjuk hozzá az alábbi importot a fájl tetejéhez:
-
-```kotlin
-import androidx.compose.foundation.lazy.items
-```
+!!!warning "items"
+ Ha hibát dobna az `items`-re, és nem találja az importot, adjuk hozzá az alábbi importot a fájl tetejéhez:
+
+ ```kotlin
+ import androidx.compose.foundation.lazy.items
+ ```
Látható, hogy a lista megjelenítésére a `LazyColumn` Composable-t használjuk, mely képes nagy elemszámú listát hatékonyan megjeleníteni. Ahhoz, hogy jól működjön a lista módosítása esetén is (pl. hozzáadás, törlés, átrendezés), mindenképp érdemes a `key` paramétert úgy definiálni, hogy az adott listaelemet egyértelműen beazonosítsa.
+Lista elemnek most egy egyszerű `ListItem`-et használunk, ami esetünkben tökéletesen elég. A *leadingContent* helyére a *priority* ikon, a *headlineContent* helyére a *todo* címe, a *supportingContent* helyére a határidő kerül. Természetesen komplexebb listaelemek esetén külön *Composable* készítése ajánlott.
+
Az oldal elkészült, már csak a navigációt kell frissíteni az oldalhoz. Vegyük fel az útvonalat a `Screen` osztályba:
```kotlin
@@ -701,9 +692,9 @@ fun NavGraph(
Futtassuk az alkalmazás!
!!!example "BEADANDÓ (1 pont)"
- Készíts egy **képernyőképet**, amelyen látszik a **futó alkalmazás** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**.
+ Készíts egy **képernyőképet**, amelyen látszik a **futó alkalmazás** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**!
- A képet a megoldásban a repository-ba f1.png néven töltsd föl.
+ A képet a megoldásban a repository-ba f1.png néven töltsd föl!
A képernyőkép szükséges feltétele a pontszám megszerzésének.
@@ -715,9 +706,9 @@ Ezen a laboron egy egyszerűsített megoldást mutatunk be a feladatok tárolás
```kotlin
package hu.bme.aut.android.todo.data
-import hu.bme.aut.android.todo.domain.Todo
+import hu.bme.aut.android.todo.domain.model.Todo
-interface TodoRepository {
+interface ITodoRepository {
suspend fun insertTodo(todo: Todo)
suspend fun deleteTodo(todo: Todo)
suspend fun getTodoById(id: Int): Todo
@@ -731,13 +722,13 @@ interface TodoRepository {
```kotlin
package hu.bme.aut.android.todo.data
-import hu.bme.aut.android.todo.domain.Priority
-import hu.bme.aut.android.todo.domain.Todo
+import hu.bme.aut.android.todo.domain.model.Priority
+import hu.bme.aut.android.todo.domain.model.Todo
import kotlinx.coroutines.delay
import kotlinx.datetime.toKotlinLocalDateTime
import java.time.LocalDateTime
-object MemoryTodoRepository : TodoRepository {
+object MemoryTodoRepository : ITodoRepository {
private val todos = mutableListOf(
Todo(
id = 1,
@@ -816,12 +807,12 @@ object MemoryTodoRepository : TodoRepository {
}
```
-A `TodoRepository` egy általános interfészt ír le, mellyel elérhetővé válnak a feladatok az alkalmazás számára, míg a `MemoryTodoRepository` egy memória alapú megvalósítását mutatja be. Bár itt most nem lenne szükség a `suspend` kulcsszó használatára, ezzel tudjuk biztosítani, hogy a későbbiekben egy adatbázis vagy hálózati `TodoRepository` elkészítése után könnyedén tudjuk migrálni a projektet, ezt a késleltetést imitáljuk a `delay()` függvény hívásával is. Az `object` kulcsszóval a _Singleton_ mintát tudjuk egyszerűen megvalósítani.
+Az `ITodoRepository` egy általános interfészt ír le, mellyel elérhetővé válnak a feladatok az alkalmazás számára, míg a `MemoryTodoRepository` egy memória alapú megvalósítását mutatja be. Bár itt most nem lenne szükség a `suspend` kulcsszó használatára, ezzel tudjuk biztosítani, hogy a későbbiekben egy adatbázis vagy hálózati `TodoRepository` elkészítése után könnyedén tudjuk migrálni a projektet, ezt a késleltetést imitáljuk a `delay()` függvény hívásával is. Az `object` kulcsszóval a _Singleton_ mintát tudjuk egyszerűen megvalósítani.
Frissítsük a `TodoListViewModel` osztályt, hogy ezt a memória alapú megvalósítást használja:
```kotlin
-class TodoListViewModel(private val repository: TodoRepository) : ViewModel() {
+class TodoListViewModel(private val repository: ITodoRepository) : ViewModel() {
private val _state = MutableStateFlow(TodoListState.Loading)
val state = _state.asStateFlow()
@@ -833,7 +824,7 @@ class TodoListViewModel(private val repository: TodoRepository) : ViewModel() {
viewModelScope.launch {
try {
_state.value = TodoListState.Loading
- delay(2000)
+ delay(2000)
val list = repository.getAllTodos()
_state.value = TodoListState.Result(
todoList = list.map { it.asTodoUi() }
@@ -939,8 +930,8 @@ import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
+import hu.bme.aut.android.todo.data.ITodoRepository
import hu.bme.aut.android.todo.data.MemoryTodoRepository
-import hu.bme.aut.android.todo.data.TodoRepository
import hu.bme.aut.android.todo.ui.model.TodoUi
import hu.bme.aut.android.todo.ui.model.asTodoUi
import kotlinx.coroutines.delay
@@ -954,7 +945,7 @@ sealed class TodoDetailState {
data class Result(val todo: TodoUi) : TodoDetailState()
}
-class TodoDetailViewModel(private val repository: TodoRepository, private val savedStateHandle: SavedStateHandle) : ViewModel() {
+class TodoDetailViewModel(private val repository: ITodoRepository, private val savedStateHandle: SavedStateHandle) : ViewModel() {
private val _state = MutableStateFlow(TodoDetailState.Loading)
val state = _state.asStateFlow()
@@ -1092,8 +1083,7 @@ fun TodoDetailScreen(
modifier = Modifier
.height(TextFieldDefaults.MinHeight)
.fillMaxWidth()
- .clip(shape = RoundedCornerShape(size = 5.dp))
- .background(color = Color.White),
+ .clip(shape = RoundedCornerShape(size = 5.dp)),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
@@ -1125,9 +1115,9 @@ fun TodoDetailScreen(
Végül a lista oldalhoz hasonlóan kiolvassuk a `ViewModel`-ben tárolt állapotot és megjelenítjük a megfelelő felületi elemeket.
!!!example "BEADANDÓ (1 pont)"
- Készíts egy **képernyőképet**, amelyen látszik a **részletes nézet** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**.
+ Készíts egy **képernyőképet**, amelyen látszik a **részletes nézet** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**!
- A képet a megoldásban a repository-ba f2.png néven töltsd föl.
+ A képet a megoldásban a repository-ba f2.png néven töltsd föl!
A képernyőkép szükséges feltétele a pontszám megszerzésének.
@@ -1214,7 +1204,7 @@ fun DateSelector(
@Preview
@Composable
-fun DateSelector() {
+fun DateSelectorPreview() {
val d = LocalDateTime.now()
DateSelector(
pickedDate = LocalDate(d.year, d.month, d.dayOfMonth),
@@ -1239,6 +1229,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
@@ -1274,6 +1265,17 @@ fun NormalTextField(
shape = shape
)
}
+
+@Preview
+@Composable
+fun NormalTextFieldPreview() {
+ NormalTextField(
+ value = "name",
+ label = "Péter",
+ onValueChange = {}) {
+
+ }
+}
```
`PriorityDropdown.kt`:
@@ -1644,8 +1646,8 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
+import hu.bme.aut.android.todo.data.ITodoRepository
import hu.bme.aut.android.todo.data.MemoryTodoRepository
-import hu.bme.aut.android.todo.data.TodoRepository
import hu.bme.aut.android.todo.ui.model.PriorityUi
import hu.bme.aut.android.todo.ui.model.TodoUi
import hu.bme.aut.android.todo.ui.model.UiText
@@ -1663,21 +1665,21 @@ data class TodoCreateState(
val todo: TodoUi = TodoUi()
)
-sealed class TodoCreateUiEvent{
+sealed class TodoCreateUiEvent {
object Success : TodoCreateUiEvent()
data class Failure(val error: UiText) : TodoCreateUiEvent()
}
sealed class TodoCreateEvent {
- data class ChangeTitle(val text: String): TodoCreateEvent()
- data class ChangeDescription(val text: String): TodoCreateEvent()
- data class SelectPriority(val priority: PriorityUi): TodoCreateEvent()
- data class SelectDate(val date: LocalDate): TodoCreateEvent()
- object SaveTodo: TodoCreateEvent()
+ data class ChangeTitle(val text: String) : TodoCreateEvent()
+ data class ChangeDescription(val text: String) : TodoCreateEvent()
+ data class SelectPriority(val priority: PriorityUi) : TodoCreateEvent()
+ data class SelectDate(val date: LocalDate) : TodoCreateEvent()
+ object SaveTodo : TodoCreateEvent()
}
class TodoCreateViewModel(
- private val todoRepository: TodoRepository
+ private val todoRepository: ITodoRepository
) : ViewModel() {
private val _state = MutableStateFlow(TodoCreateState())
@@ -1687,32 +1689,49 @@ class TodoCreateViewModel(
val uiEvent = _uiEvent.receiveAsFlow()
fun onEvent(event: TodoCreateEvent) {
- when(event) {
+ when (event) {
is TodoCreateEvent.ChangeTitle -> {
val newValue = event.text
- _state.update { it.copy(
- todo = it.todo.copy(title = newValue)
- ) }
+ _state.update {
+ it.copy(
+ todo = it.todo.copy(title = newValue)
+ )
+ }
}
+
is TodoCreateEvent.ChangeDescription -> {
val newValue = event.text
- _state.update { it.copy(
- todo = it.todo.copy(description = newValue)
- ) }
+ _state.update {
+ it.copy(
+ todo = it.todo.copy(description = newValue)
+ )
+ }
}
+
is TodoCreateEvent.SelectPriority -> {
val newValue = event.priority
- _state.update { it.copy(
- todo = it.todo.copy(priority = newValue)
- ) }
+ _state.update {
+ it.copy(
+ todo = it.todo.copy(priority = newValue)
+ )
+ }
}
+
is TodoCreateEvent.SelectDate -> {
val newValue = event.date
- _state.update { it.copy(
- todo = it.todo.copy(dueDate = newValue.toString())
- ) }
+ _state.update {
+ it.copy(
+ todo = it.todo.copy(dueDate = newValue.toString())
+ )
+ }
}
+
TodoCreateEvent.SaveTodo -> {
+ _state.update {
+ it.copy(
+ todo = it.todo.copy(id = (Math.random()*Int.MAX_VALUE).toInt())
+ )
+ }
onSave()
}
}
@@ -1774,7 +1793,7 @@ import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import hu.bme.aut.android.todo.R
-import hu.bme.aut.android.todo.domain.Priority
+import hu.bme.aut.android.todo.domain.model.Priority
import hu.bme.aut.android.todo.ui.common.TodoAppBar
import hu.bme.aut.android.todo.ui.common.TodoEditor
import hu.bme.aut.android.todo.ui.model.asPriorityUi
@@ -1923,9 +1942,9 @@ fun NavGraph(
Próbáljuk ki az alkalmazást! Mit tapasztalunk egy feladat létrehozásánál?
!!!example "BEADANDÓ (1 pont)"
- Készíts egy **képernyőképet**, amelyen látszik a **feladat létrehozása nézet** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**.
+ Készíts egy **képernyőképet**, amelyen látszik a **feladat létrehozása nézet** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**!
- A képet a megoldásban a repository-ba f3.png néven töltsd föl.
+ A képet a megoldásban a repository-ba f3.png néven töltsd föl!
A képernyőkép szükséges feltétele a pontszám megszerzésének.
@@ -1942,6 +1961,7 @@ Mi most a harmadik megoldást fogjuk alkalmazni. Ehhez értesülnünk kell arró
`TodoListScreen.kt`:
```kotlin
+@Composable
fun TodoListScreen(
onListItemClick: (Int) -> Unit,
onFabClick: () -> Unit,
@@ -1963,7 +1983,7 @@ fun TodoListScreen(
}
}
- Scaffold(
+ Scaffold(
...
}
```
@@ -2104,9 +2124,9 @@ Valósítsuk meg a felugró dátumválasztó ablakot a `TodoCreateScreen`-en! Ko
```
!!!example "BEADANDÓ (1 pont)"
- Készíts egy **képernyőképet**, amelyen látszik a **dátumválasztó dialógus** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**.
+ Készíts egy **képernyőképet**, amelyen látszik a **dátumválasztó dialógus** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **ahhoz tartozó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**!
- A képet a megoldásban a repository-ba f4.png néven töltsd föl.
+ A képet a megoldásban a repository-ba f4.png néven töltsd föl!
A képernyőkép szükséges feltétele a pontszám megszerzésének.
@@ -2115,9 +2135,9 @@ Valósítsuk meg a felugró dátumválasztó ablakot a `TodoCreateScreen`-en! Ko
Adjunk hozzá egy függvényt a `TodoListViewModel`-hez, mely megkeveri a lista elemeit! Használjuk ehhez a `shuffled()` függvényt. Hívjuk meg ezt a függvényt egy új _floating action button_ megnyomására (tegyük egy `Column`-be a létrehozás gombot, és fölé tegyünk egy új gombot). Vegyük fel a listán belül a `ListItem` `modifier` láncához az `animateItemPlacement()` hívást. Mit tapasztalunk, ha így megkeverjük a lista tartalmát? Mi történik, ha kivesszük a `LazyColumn` `items` blokkjából a `key` paramétert?
!!!example "BEADANDÓ (1 pont)"
- Készíts egy **képernyőképet**, amelyen látszik a **lista megkevert állapotában** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **animációt tartalmazó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**.
+ Készíts egy **képernyőképet**, amelyen látszik a **lista megkevert állapotában** (emulátoron, készüléket tükrözve vagy képernyőfelvétellel), az **animációt tartalmazó kódrészlet**, valamint a **neptun kódod a kódban valahol kommentként**!
- A képet a megoldásban a repository-ba f5.png néven töltsd föl.
+ A képet a megoldásban a repository-ba f5.png néven töltsd föl!
A képernyőkép szükséges feltétele a pontszám megszerzésének.