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

View file

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

View file

@ -1,46 +1,99 @@
package com.faraphel.tasks_valider package com.faraphel.tasks_valider
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize import androidx.annotation.RequiresApi
import androidx.compose.material3.MaterialTheme import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Surface import androidx.room.Room
import androidx.compose.material3.Text import androidx.room.RoomDatabase
import androidx.compose.runtime.Composable import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.compose.ui.Modifier import com.faraphel.tasks_valider.database.Database
import androidx.compose.ui.tooling.preview.Preview import com.faraphel.tasks_valider.database.entities.Group
import com.faraphel.tasks_valider.ui.theme.TasksvaliderTheme 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() { class MainActivity : ComponentActivity() {
companion object {
lateinit var database: Database
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) 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 // Reset the database
fun Greeting(name: String, modifier: Modifier = Modifier) { this.deleteDatabase("local") // TODO: remove
Text(
text = "Hello $name!", // Create the database
modifier = modifier 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 { plugins {
id("com.android.application") version "8.2.2" apply false id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" 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
} }