Merge pull request 'base for the database' (#5) from database into main

Reviewed-on: faraphel/Study-M1-PDS#5
This commit is contained in:
faraphel 2024-03-14 21:36:09 +00:00
commit 41cd34cbb9
25 changed files with 557 additions and 36 deletions

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="selectedTabId" value="Android Vitals" />
</component>
</project>

View file

@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
}
android {
@ -50,7 +51,6 @@ android {
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
@ -59,6 +59,7 @@ dependencies {
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.room:room-ktx:2.6.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
@ -66,4 +67,5 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
ksp("androidx.room:room-compiler:2.6.1")
}

View file

@ -15,7 +15,6 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Tasksvalider">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View file

@ -1,46 +1,99 @@
package com.faraphel.tasks_valider
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.faraphel.tasks_valider.ui.theme.TasksvaliderTheme
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.faraphel.tasks_valider.database.Database
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.GroupStudent
import com.faraphel.tasks_valider.database.entities.Student
import com.faraphel.tasks_valider.database.entities.Task
import com.faraphel.tasks_valider.database.entities.TaskGroup
import com.faraphel.tasks_valider.database.entities.Teacher
import com.faraphel.tasks_valider.ui.widgets.WidgetTaskStudent
import java.time.Instant
class MainActivity : ComponentActivity() {
companion object {
lateinit var database: Database
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TasksvaliderTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
// Reset the database
this.deleteDatabase("local") // TODO: remove
// Create the database
database = Room.databaseBuilder(
this.applicationContext,
Database::class.java, "local"
)
.allowMainThreadQueries() // TODO: remove
.build()
// Create some data
val studentDao = database.studentDao()
studentDao.insert(
Student(firstName = "John", lastName = "Joe"),
Student(firstName = "Evan", lastName = "Doe"),
Student(firstName = "Xavier", lastName = "Moe"),
)
val teacherDao = database.teacherDao()
teacherDao.insert(
Teacher(firstName = "Jean", lastName = "Voe")
)
val groupDao = database.groupDao()
groupDao.insert(
Group(name = "Group 1"),
Group(name = "Group 2"),
)
val groupStudentDao = database.groupStudentDao()
groupStudentDao.insert(
GroupStudent(1, 1),
GroupStudent(1, 2),
GroupStudent(2, 3),
)
val taskDao = database.taskDao()
taskDao.insert(
Task(title = "Task 1", description = "Do something"),
Task(title = "Task 2", description = "Do something else"),
Task(title = "Task 3", description = "Do something nice"),
)
val taskGroupDao = database.taskGroupDao()
taskGroupDao.insert(
TaskGroup(1, 1),
TaskGroup(2, 2, true, 1, Instant.now()),
)
/*
// display some data
this.setContent {
Column {
database.taskGroupDao().getAll().forEach { taskGroup ->
WidgetTaskStudent(database, taskGroup)
}
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
TasksvaliderTheme {
Greeting("Android")
*/
}
}

View file

@ -0,0 +1,50 @@
package com.faraphel.tasks_valider.database
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.room.Database
import androidx.room.DatabaseConfiguration
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.faraphel.tasks_valider.database.converters.InstantConverter
import com.faraphel.tasks_valider.database.dao.GroupDao
import com.faraphel.tasks_valider.database.dao.GroupStudentDao
import com.faraphel.tasks_valider.database.dao.StudentDao
import com.faraphel.tasks_valider.database.dao.TaskDao
import com.faraphel.tasks_valider.database.dao.TaskGroupDao
import com.faraphel.tasks_valider.database.dao.TeacherDao
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.GroupStudent
import com.faraphel.tasks_valider.database.entities.Student
import com.faraphel.tasks_valider.database.entities.Task
import com.faraphel.tasks_valider.database.entities.TaskGroup
import com.faraphel.tasks_valider.database.entities.Teacher
import java.time.Instant
@Database(
entities = [
Group::class,
Student::class,
Teacher::class,
Task::class,
GroupStudent::class,
TaskGroup::class,
],
version = 1
)
@TypeConverters(
InstantConverter::class
)
abstract class Database : RoomDatabase() {
// entities
abstract fun groupDao(): GroupDao
abstract fun studentDao(): StudentDao
abstract fun teacherDao(): TeacherDao
abstract fun taskDao(): TaskDao
// relations
abstract fun groupStudentDao(): GroupStudentDao
abstract fun taskGroupDao(): TaskGroupDao
}

View file

@ -0,0 +1,20 @@
package com.faraphel.tasks_valider.database.converters
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.room.TypeConverter
import java.time.Instant
class InstantConverter {
@RequiresApi(Build.VERSION_CODES.O)
@TypeConverter
fun fromTimestamp(value: Long?): Instant? {
return value?.let { Instant.ofEpochMilli(it) }
}
@RequiresApi(Build.VERSION_CODES.O)
@TypeConverter
fun dateToTimestamp(instant: Instant?): Long? {
return instant?.toEpochMilli()
}
}

View file

@ -0,0 +1,29 @@
package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.Student
@Dao
interface GroupDao : BaseDao<Group> {
@Query("SELECT * FROM `groups`")
override fun getAll(): List<Group>
@Query("SELECT * FROM `groups` WHERE id = :id")
fun getById(id: Long): Group
/**
Allow to get all groups with a specific student
*/
@Query(
"SELECT * FROM `groups` " +
"JOIN `group_student` ON `groups`.id = `group_student`.student_id " +
"WHERE `group_student`.student_id = :studentId"
)
@RewriteQueriesToDropUnusedColumns
fun filterByStudentId(studentId: Long): List<Group>
}

View file

@ -0,0 +1,19 @@
package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.GroupStudent
import com.faraphel.tasks_valider.database.entities.Student
@Dao
interface GroupStudentDao : BaseDao<GroupStudent> {
@Query("SELECT * FROM `group_student`")
override fun getAll(): List<GroupStudent>
@Query("SELECT * FROM `group_student` WHERE group_id = :groupId AND student_id = :studentId")
fun getById(groupId: Long, studentId: Long): GroupStudent
}

View file

@ -0,0 +1,32 @@
package com.faraphel.tasks_valider.database.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.Person
import com.faraphel.tasks_valider.database.entities.Student
@Dao
interface StudentDao : BaseDao<Student> {
@Query("SELECT * FROM `students`")
override fun getAll(): List<Student>
@Query("SELECT * FROM `students` WHERE id = :id")
fun getById(id: Long): Student
/**
Allow to get all the students in a group
*/
@Query(
"SELECT * FROM `students` " +
"JOIN `group_student` ON `students`.id = `group_student`.student_id " +
"WHERE `group_student`.group_id = :groupId"
)
@RewriteQueriesToDropUnusedColumns
fun filterByGroupId(groupId: Long): List<Student>
}

View file

@ -0,0 +1,29 @@
package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.Task
@Dao
interface TaskDao : BaseDao<Task> {
@Query("SELECT * FROM `tasks`")
override fun getAll(): List<Task>
@Query("SELECT * FROM `tasks` WHERE id = :id")
fun getById(id: Long): Task
/**
Get all the tasks for a specific group
*/
@Query(
"SELECT * FROM `tasks` " +
"JOIN `task_group` ON `tasks`.id = `task_group`.task_id " +
"WHERE `task_group`.group_id = :groupId"
)
@RewriteQueriesToDropUnusedColumns
fun filterByGroupId(groupId: Long): List<Task>
}

View file

@ -0,0 +1,16 @@
package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.entities.TaskGroup
@Dao
interface TaskGroupDao : BaseDao<TaskGroup> {
@Query("SELECT * FROM `task_group`")
override fun getAll(): List<TaskGroup>
@Query("SELECT * FROM `task_group` WHERE task_id = :taskId AND group_id = :groupId")
fun getById(taskId: Long, groupId: Long): TaskGroup
}

View file

@ -0,0 +1,18 @@
package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.entities.Person
import com.faraphel.tasks_valider.database.entities.Student
import com.faraphel.tasks_valider.database.entities.Teacher
@Dao
interface TeacherDao : BaseDao<Teacher> {
@Query("SELECT * FROM `teachers`")
override fun getAll(): List<Teacher>
@Query("SELECT * FROM `teachers` WHERE id = :id")
fun getById(id: Long): Teacher
}

View file

@ -0,0 +1,19 @@
package com.faraphel.tasks_valider.database.dao.base
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Update
interface BaseDao<Entity> {
@Insert
fun insert(vararg entities: Entity): List<Long>
@Update
fun update(vararg entities: Entity): Int
@Delete
fun delete(vararg entities: Entity): Int
fun getAll(): List<Entity>
}

View file

@ -0,0 +1,12 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "groups")
data class Group (
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo("name") val name: String? = null,
)

View file

@ -0,0 +1,33 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.database.entities.Student
@Entity(
tableName = "group_student",
primaryKeys = [
"group_id",
"student_id"
],
foreignKeys = [
ForeignKey(
entity = Group::class,
parentColumns = ["id"],
childColumns = ["group_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Student::class,
parentColumns = ["id"],
childColumns = ["student_id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class GroupStudent(
@ColumnInfo("group_id", index = true) val groupId: Long,
@ColumnInfo("student_id", index = true) val studentId: Long,
)

View file

@ -0,0 +1,18 @@
package com.faraphel.tasks_valider.database.entities
import java.util.Locale
open class Person (
open val id: Long = 0,
open val firstName: String,
open val lastName: String,
) {
/**
Get the full name of the person
*/
val fullName: String
get() {
return "${firstName.capitalize(Locale.ROOT)} ${lastName.uppercase()}"
}
}

View file

@ -0,0 +1,12 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "students")
class Student(
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0,
@ColumnInfo("first_name") override val firstName: String,
@ColumnInfo("last_name") override val lastName: String
) : Person(id, firstName, lastName)

View file

@ -0,0 +1,12 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tasks")
data class Task (
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo("title") val title: String,
@ColumnInfo("description") val description: String,
)

View file

@ -0,0 +1,42 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import java.time.Instant
@Entity(
tableName = "task_group",
primaryKeys = [
"task_id",
"group_id"
],
foreignKeys = [
ForeignKey(
entity = Group::class,
parentColumns = ["id"],
childColumns = ["group_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Task::class,
parentColumns = ["id"],
childColumns = ["task_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Teacher::class,
parentColumns = ["id"],
childColumns = ["approval_teacher_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class TaskGroup (
@ColumnInfo("task_id") val taskId: Long,
@ColumnInfo("group_id") val groupId: Long,
@ColumnInfo("approval_status") var approvalStatus: Boolean = false,
@ColumnInfo("approval_teacher_id") val approvalTeacherId: Long? = null,
@ColumnInfo("approval_time") val approvalTime: Instant? = null
)

View file

@ -0,0 +1,12 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "teachers")
class Teacher(
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0,
@ColumnInfo("first_name") override val firstName: String,
@ColumnInfo("last_name") override val lastName: String
) : Person(id, firstName, lastName)

View file

@ -0,0 +1,18 @@
package com.faraphel.tasks_valider.ui.widgets
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.faraphel.tasks_valider.database.entities.Group
@Composable
fun WidgetGroup(group: Group) {
// TODO
Column {
Text(text = group.name!!)
// group.tasks.forEach { task ->
// WidgetTask(task)
// }
}
}

View file

@ -0,0 +1,15 @@
package com.faraphel.tasks_valider.ui.widgets
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.faraphel.tasks_valider.database.entities.Task
@Composable
fun WidgetTask(task: Task) {
// task information
Column {
Text(text = task.title)
Text(text = task.description)
}
}

View file

@ -0,0 +1,54 @@
package com.faraphel.tasks_valider.ui.widgets
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.faraphel.tasks_valider.database.Database
import com.faraphel.tasks_valider.database.entities.TaskGroup
@Composable
fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) {
val teacherDao = database.teacherDao()
Column {
// row for this task
Row {
// task information
// TODO: WidgetTask(task = taskStudent.task)
// align the other columns to the right
Spacer(modifier = Modifier.weight(1f))
// task status
Checkbox(
checked = taskStudent.approvalStatus,
onCheckedChange = { status -> taskStudent.approvalStatus = status }
)
}
// if the task has been approved
if (taskStudent.approvalStatus) {
Row (
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center
) {
// teacher who approved the task
Text(text = teacherDao.getById(taskStudent.approvalTeacherId!!).fullName)
// align the other columns to the right
Spacer(modifier = Modifier.width(16.dp))
// date of approval
Text(text = taskStudent.approvalTime.toString())
}
}
}
}

View file

@ -2,4 +2,5 @@
plugins {
id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false
}