diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd90055..25e579f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") } android { @@ -9,7 +10,7 @@ android { defaultConfig { applicationId = "com.example.myapplication" - minSdk = 24 + minSdk = 26 targetSdk = 33 versionCode = 1 versionName = "1.0" @@ -27,17 +28,17 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.3" + kotlinCompilerExtensionVersion = "1.4.5" } packaging { resources { @@ -46,9 +47,16 @@ android { } } +kotlin { + jvmToolchain(17) +} + dependencies { + //Core implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + + //UI implementation("androidx.activity:activity-compose:1.7.2") implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.navigation:navigation-compose:2.6.0") @@ -57,6 +65,16 @@ dependencies { implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") + + // Room + val room_version = "2.5.2" + implementation("androidx.room:room-runtime:$room_version") + annotationProcessor("androidx.room:room-compiler:$room_version") + ksp("androidx.room:room-compiler:$room_version") + implementation("androidx.room:room-ktx:$room_version") + implementation("androidx.room:room-paging:$room_version") + + //Tests testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/java/com/example/myapplication/category/composeui/CategotyList.kt b/app/src/main/java/com/example/myapplication/category/composeui/CategotyList.kt index 6a41ffd..0333199 100644 --- a/app/src/main/java/com/example/myapplication/category/composeui/CategotyList.kt +++ b/app/src/main/java/com/example/myapplication/category/composeui/CategotyList.kt @@ -10,22 +10,40 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController -import com.example.myapplication.category.model.getTestCategory +import com.example.myapplication.category.model.Category +import com.example.myapplication.database.AppDatabase import com.example.myapplication.ui.theme.MyApplicationTheme +import com.example.myapplication.user.model.User +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun CategoriesList(navController: NavController?) { + val context = LocalContext.current; + val categories = remember { mutableStateListOf() } + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + AppDatabase.getInstance(context).categoryDao().getAll().collect { data -> + categories.clear() + categories.addAll(data) + } + } + } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .padding(all = 10.dp) .verticalScroll(rememberScrollState())) { - getTestCategory().forEachIndexed() { _, category -> + categories.forEachIndexed() { _, category -> Row(Modifier.padding(all = 20.dp)) { Text(category.category_name) } diff --git a/app/src/main/java/com/example/myapplication/category/dao/CategoryDao.kt b/app/src/main/java/com/example/myapplication/category/dao/CategoryDao.kt new file mode 100644 index 0000000..ea16783 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/category/dao/CategoryDao.kt @@ -0,0 +1,27 @@ +package com.example.myapplication.category.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Delete +import androidx.room.Update +import com.example.myapplication.category.model.Category +import kotlinx.coroutines.flow.Flow + +@Dao +interface CategoryDao { + @Query("select * from categories order by category_name collate nocase asc") + fun getAll(): Flow> + + @Query("select * from categories where categories.uid = :uid") + suspend fun getByUid(uid: Int): Category + + @Insert + suspend fun insert(category: Category) + + @Update + suspend fun update(category: Category) + + @Delete + suspend fun delete(category: Category) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/category/model/Category.kt b/app/src/main/java/com/example/myapplication/category/model/Category.kt index 5e69fa7..9c02c8d 100644 --- a/app/src/main/java/com/example/myapplication/category/model/Category.kt +++ b/app/src/main/java/com/example/myapplication/category/model/Category.kt @@ -1,15 +1,11 @@ package com.example.myapplication.category.model -import java.io.Serializable +import androidx.room.Entity +import androidx.room.PrimaryKey +@Entity(tableName = "categories") data class Category( + @PrimaryKey val uid: Int, val category_name: String, -) : Serializable +) -fun getTestCategory(): List { - return listOf( - Category("Отжимания"), - Category("Кардио"), - Category("Силовые") - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/challenge/dao/ChallengeDao.kt b/app/src/main/java/com/example/myapplication/challenge/dao/ChallengeDao.kt new file mode 100644 index 0000000..3c34636 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/challenge/dao/ChallengeDao.kt @@ -0,0 +1,27 @@ +package com.example.myapplication.challenge.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Delete +import androidx.room.Update +import com.example.myapplication.challenge.model.Challenge +import kotlinx.coroutines.flow.Flow + +@Dao +interface ChallengeDao { + @Query("select * from challenges order by challenge_name asc") + fun getAll(): Flow> + + @Query("select * from challenges where challenges.challenge_id = :uid") + suspend fun getByUid(uid: Int): Challenge + + @Insert + suspend fun insert(challenge: Challenge) + + @Update + suspend fun update(challenge: Challenge) + + @Delete + suspend fun delete(challenge: Challenge) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/challenge/model/Challenge.kt b/app/src/main/java/com/example/myapplication/challenge/model/Challenge.kt index 071a672..019dc6b 100644 --- a/app/src/main/java/com/example/myapplication/challenge/model/Challenge.kt +++ b/app/src/main/java/com/example/myapplication/challenge/model/Challenge.kt @@ -1,19 +1,33 @@ package com.example.myapplication.challenge.model +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.PrimaryKey import com.example.myapplication.category.model.Category -import java.io.Serializable +import com.example.myapplication.user.model.User +@Entity(tableName = "challenges", foreignKeys = [ + ForeignKey( + entity = User::class, + parentColumns = ["uid"], + childColumns = ["user_id"], + onDelete = ForeignKey.RESTRICT, + onUpdate = ForeignKey.RESTRICT, + ), + ForeignKey( + entity = Category::class, + parentColumns = ["uid"], + childColumns = ["category_id"], + onDelete = ForeignKey.RESTRICT, + onUpdate = ForeignKey.RESTRICT, + ), +]) data class Challenge( + @PrimaryKey val challenge_id: Int, val challenge_name: String, val challenge_status: Boolean, - val category: Category, -) : Serializable - -fun getTestChallenge(): List { - val сategory = Category("Отжимания") - return listOf( - Challenge("Сделать 20 отжиманий", false, сategory), - Challenge("Сделать 10 отжиманий", false, сategory), - Challenge("Сделать 5 отжиманий", false, сategory), - ) -} \ No newline at end of file + val category_id: Int, + @ColumnInfo(index = true) + val user_id: Int, +) diff --git a/app/src/main/java/com/example/myapplication/database/AppDatabase.kt b/app/src/main/java/com/example/myapplication/database/AppDatabase.kt new file mode 100644 index 0000000..14f34bd --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/AppDatabase.kt @@ -0,0 +1,79 @@ +package com.example.myapplication.database + +import java.util.concurrent.Flow + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.db.SupportSQLiteDatabase +import com.example.myapplication.category.dao.CategoryDao +import com.example.myapplication.category.model.Category +import com.example.myapplication.user.model.UserWithChallenges +import com.example.myapplication.challenge.dao.ChallengeDao +import com.example.myapplication.challenge.model.Challenge +import com.example.myapplication.user.dao.UserDao +import com.example.myapplication.user.model.User +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.time.LocalDate + +@Database(entities = [User::class, Challenge::class, Category::class], version = 1, exportSchema = false) +abstract class AppDatabase : RoomDatabase() { + abstract fun challengeDao(): ChallengeDao + abstract fun categoryDao(): CategoryDao + abstract fun userDao(): UserDao + + + + companion object { + private const val DB_NAME: String = "sport-app-db" + + @Volatile + private var INSTANCE: AppDatabase? = null + + private suspend fun populateDatabase() { + INSTANCE?.let { database -> + + val categoryDao = database.categoryDao() + val category1 = Category(3, "Кардио") + val category2 = Category(4, "Силовые тренировки") + categoryDao.insert(category1) + categoryDao.insert(category2) + + val userDao = database.userDao() + val user1 = User(3, "test1", "1234", "John") + val user2 = User(4, "test2", "1234", "Ivan") + userDao.insert(user1) + userDao.insert(user2) + + val challengeDao = database.challengeDao() + val challenge1 = Challenge(3, "Сделать 10 отжиманий", false, category2.uid, user1.uid) + val challenge2 = Challenge(4, "Пробежать 1 км", true, category1.uid, user2.uid) + challengeDao.insert(challenge1) + challengeDao.insert(challenge2) + } + } + + fun getInstance(appContext: Context): AppDatabase { + return INSTANCE ?: synchronized(this) { + Room.databaseBuilder( + appContext, + AppDatabase::class.java, + DB_NAME + ) + .addCallback(object : RoomDatabase.Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + CoroutineScope(Dispatchers.IO).launch { + populateDatabase() + } + } + }) + .build() + .also { INSTANCE = it } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/user/composeui/UserView.kt b/app/src/main/java/com/example/myapplication/user/composeui/UserView.kt index 6738f69..89a25c7 100644 --- a/app/src/main/java/com/example/myapplication/user/composeui/UserView.kt +++ b/app/src/main/java/com/example/myapplication/user/composeui/UserView.kt @@ -13,47 +13,69 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember 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.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.example.myapplication.user.model.getTestUser import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.R +import com.example.myapplication.database.AppDatabase +import com.example.myapplication.user.model.UserWithChallenges +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @OptIn(ExperimentalMaterial3Api::class) @Composable -fun UserView(id: Int) { - var user = getTestUser()[id] - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .padding(all = 10.dp) - .verticalScroll(rememberScrollState())) { - OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = user.login, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.user_login)) + fun UserView(id: Int) { + val context = LocalContext.current + val (userWithChallanges, setUserWithChallanges) = remember { mutableStateOf(null) } + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + setUserWithChallanges(AppDatabase.getInstance(context).userDao().getByUid(id)) } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = user.fio, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.user_fio)) - } - ) - user.challenges.forEachIndexed() { _, challenge -> - Row { - Text(text = challenge.challenge_name) - if (challenge.challenge_status) { - Text(text = " - Выполнено") + } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .padding(all = 10.dp) + .verticalScroll(rememberScrollState()) + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = userWithChallanges?.user?.login.toString(), + onValueChange = {}, + readOnly = true, + label = { + Text(stringResource(id = R.string.user_login)) } - else { - Text(text = " - В процессе") + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = userWithChallanges?.user?.fio.toString(), + onValueChange = {}, + readOnly = true, + label = { + Text(stringResource(id = R.string.user_fio)) + } + ) + userWithChallanges?.challenges?.forEachIndexed() { _, challenge -> + Row { + Text(text = challenge.challenge_name) + if (challenge.challenge_status) { + Text(text = " - Выполнено") + } else { + Text(text = " - В процессе") + } } } } } -} + @Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) diff --git a/app/src/main/java/com/example/myapplication/user/composeui/UsersList.kt b/app/src/main/java/com/example/myapplication/user/composeui/UsersList.kt index 114dd28..297621f 100644 --- a/app/src/main/java/com/example/myapplication/user/composeui/UsersList.kt +++ b/app/src/main/java/com/example/myapplication/user/composeui/UsersList.kt @@ -12,24 +12,42 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.example.myapplication.composeui.navigation.Screen -import com.example.myapplication.user.model.getTestUser +import com.example.myapplication.database.AppDatabase import com.example.myapplication.ui.theme.MyApplicationTheme +import com.example.myapplication.user.model.User +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.withContext @Composable fun UsersList(navController: NavController?) { + val context = LocalContext.current; + val users = remember { mutableStateListOf() } + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + AppDatabase.getInstance(context).userDao().getAll().collect { data -> + users.clear() + users.addAll(data) + } + } + } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .padding(all = 10.dp) .verticalScroll(rememberScrollState())) { - getTestUser().forEachIndexed() { _, user -> - val userId = Screen.UserView.route.replace("{id}", (user.id).toString()) + users.forEachIndexed() { _, user -> + val userId = Screen.UserView.route.replace("{id}", (user.uid).toString()) Row(Modifier.padding(all = 10.dp)) { Button( modifier = Modifier diff --git a/app/src/main/java/com/example/myapplication/user/dao/UserDao.kt b/app/src/main/java/com/example/myapplication/user/dao/UserDao.kt new file mode 100644 index 0000000..e67a3fc --- /dev/null +++ b/app/src/main/java/com/example/myapplication/user/dao/UserDao.kt @@ -0,0 +1,28 @@ +package com.example.myapplication.user.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.example.myapplication.user.model.User +import com.example.myapplication.user.model.UserWithChallenges +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserDao { + @Query("select * from users order by fio asc") + fun getAll(): Flow> + + @Query("select * from users where users.uid = :uid") + suspend fun getByUid(uid: Int): UserWithChallenges + + @Insert + suspend fun insert(user: User) + + @Update + suspend fun update(user: User) + + @Delete + suspend fun delete(user: User) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/user/model/User.kt b/app/src/main/java/com/example/myapplication/user/model/User.kt index 5c69529..97fd69b 100644 --- a/app/src/main/java/com/example/myapplication/user/model/User.kt +++ b/app/src/main/java/com/example/myapplication/user/model/User.kt @@ -1,22 +1,13 @@ package com.example.myapplication.user.model +import androidx.room.Entity +import androidx.room.PrimaryKey import com.example.myapplication.challenge.model.Challenge -import com.example.myapplication.challenge.model.getTestChallenge -import java.io.Serializable +@Entity(tableName = "users") data class User( - val id: Int, + @PrimaryKey val uid: Int, val login: String, val password: String, val fio: String, - val challenges: List, -) : Serializable - -fun getTestUser(): List { - val challenges = getTestChallenge() - return listOf( - User(0,"user1", "1234", "ivanov ivan", challenges), - User(1,"user2", "1234", "vasiliev ivan", challenges), - User(2,"user3", "1234", "listov ivan", challenges), - ) -} \ No newline at end of file +) diff --git a/app/src/main/java/com/example/myapplication/user/model/UserWithChallenges.kt b/app/src/main/java/com/example/myapplication/user/model/UserWithChallenges.kt new file mode 100644 index 0000000..2dbd882 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/user/model/UserWithChallenges.kt @@ -0,0 +1,15 @@ +package com.example.myapplication.user.model + +import androidx.room.Embedded +import androidx.room.Relation +import com.example.myapplication.challenge.model.Challenge +import com.example.myapplication.user.model.User + +data class UserWithChallenges( + @Embedded val user: User, + @Relation( + parentColumn = "uid", + entityColumn = "user_id" + ) + val challenges: List +) diff --git a/build.gradle.kts b/build.gradle.kts index 9861456..bd1eb8a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,5 @@ plugins { id("com.android.application") version "8.1.1" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false + id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false } \ No newline at end of file