Cool
This commit is contained in:
parent
f68ea9109f
commit
c5b54562d3
@ -66,6 +66,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:1.1.2")
|
implementation("androidx.compose.material3:material3:1.1.2")
|
||||||
|
implementation("com.google.firebase:protolite-well-known-types:18.0.0")
|
||||||
|
|
||||||
// Room
|
// Room
|
||||||
val room_version = "2.5.2"
|
val room_version = "2.5.2"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".StudentApplication"
|
android:name=".TaskApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
@ -4,7 +4,7 @@ import android.app.Application
|
|||||||
import ru.ulstu.`is`.pmu.database.AppContainer
|
import ru.ulstu.`is`.pmu.database.AppContainer
|
||||||
import ru.ulstu.`is`.pmu.database.AppDataContainer
|
import ru.ulstu.`is`.pmu.database.AppDataContainer
|
||||||
|
|
||||||
class StudentApplication : Application() {
|
class TaskApplication : Application() {
|
||||||
lateinit var container: AppContainer
|
lateinit var container: AppContainer
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
package ru.ulstu.`is`.pmu.database
|
package ru.ulstu.`is`.pmu.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.GroupRepository
|
import ru.ulstu.`is`.pmu.database.task.repository.UserRepository
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.OfflineGroupRepository
|
import ru.ulstu.`is`.pmu.database.task.repository.OfflineUserRepository
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.OfflineStudentRepository
|
import ru.ulstu.`is`.pmu.database.task.repository.OfflineTaskRepository
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.StudentRepository
|
import ru.ulstu.`is`.pmu.database.task.repository.TaskRepository
|
||||||
|
|
||||||
interface AppContainer {
|
interface AppContainer {
|
||||||
val studentRepository: StudentRepository
|
val taskRepository: TaskRepository
|
||||||
val groupRepository: GroupRepository
|
val userRepository: UserRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppDataContainer(private val context: Context) : AppContainer {
|
class AppDataContainer(private val context: Context) : AppContainer {
|
||||||
override val studentRepository: StudentRepository by lazy {
|
override val taskRepository: TaskRepository by lazy {
|
||||||
OfflineStudentRepository(AppDatabase.getInstance(context).studentDao())
|
OfflineTaskRepository(AppDatabase.getInstance(context).taskDao())
|
||||||
}
|
}
|
||||||
override val groupRepository: GroupRepository by lazy {
|
override val userRepository: UserRepository by lazy {
|
||||||
OfflineGroupRepository(AppDatabase.getInstance(context).groupDao())
|
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -8,15 +8,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.ulstu.`is`.pmu.database.student.dao.GroupDao
|
import ru.ulstu.`is`.pmu.database.task.dao.TaskDao
|
||||||
import ru.ulstu.`is`.pmu.database.student.dao.StudentDao
|
import ru.ulstu.`is`.pmu.database.task.dao.UserDao
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Group
|
import ru.ulstu.`is`.pmu.database.task.model.User
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
|
||||||
@Database(entities = [Student::class, Group::class], version = 1, exportSchema = false)
|
@Database(entities = [Task::class, User::class], version = 1, exportSchema = false)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun studentDao(): StudentDao
|
abstract fun taskDao(): TaskDao
|
||||||
abstract fun groupDao(): GroupDao
|
abstract fun userDao(): UserDao
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DB_NAME: String = "pmy-db"
|
private const val DB_NAME: String = "pmy-db"
|
||||||
@ -26,26 +26,22 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
private suspend fun populateDatabase() {
|
private suspend fun populateDatabase() {
|
||||||
INSTANCE?.let { database ->
|
INSTANCE?.let { database ->
|
||||||
// Groups
|
// Users
|
||||||
val groupDao = database.groupDao()
|
val userDao = database.userDao()
|
||||||
val group1 = Group(1, "Группа 1")
|
val user1 = User(1, "Sergey", "brook.sergey@gmail.com")
|
||||||
val group2 = Group(2, "Группа 2")
|
userDao.insert(user1)
|
||||||
val group3 = Group(3, "Группа 3")
|
// Tasks
|
||||||
groupDao.insert(group1)
|
val taskDao = database.taskDao()
|
||||||
groupDao.insert(group2)
|
val task1 = Task("First1", "Last1","12.12.2023",false, user1)
|
||||||
groupDao.insert(group3)
|
val task2 = Task("First2", "Last2","15.12.2023",false, user1)
|
||||||
// Students
|
val task3 = Task("First3", "Last3","10.12.2023",false, user1)
|
||||||
val studentDao = database.studentDao()
|
val task4 = Task("First4", "Last4","31.12.2023",false, user1)
|
||||||
val student1 = Student("First1", "Last1", group1, "+79998887761", "st1@m.ru")
|
val task5 = Task("First5", "Last5","05.12.2023",false, user1)
|
||||||
val student2 = Student("First2", "Last2", group2, "+79998887762", "st2@m.ru")
|
taskDao.insert(task1)
|
||||||
val student3 = Student("First3", "Last3", group3, "+79998887763", "st3@m.ru")
|
taskDao.insert(task2)
|
||||||
val student4 = Student("First4", "Last4", group3, "+79998887764", "st4@m.ru")
|
taskDao.insert(task3)
|
||||||
val student5 = Student("First5", "Last5", group2, "+79998887765", "st5@m.ru")
|
taskDao.insert(task4)
|
||||||
studentDao.insert(student1)
|
taskDao.insert(task5)
|
||||||
studentDao.insert(student2)
|
|
||||||
studentDao.insert(student3)
|
|
||||||
studentDao.insert(student4)
|
|
||||||
studentDao.insert(student5)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Delete
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Update
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Group
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface GroupDao {
|
|
||||||
@Query("select * from groups order by group_name collate nocase asc")
|
|
||||||
suspend fun getAll(): List<Group>
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
suspend fun insert(group: Group)
|
|
||||||
|
|
||||||
@Update
|
|
||||||
suspend fun update(group: Group)
|
|
||||||
|
|
||||||
@Delete
|
|
||||||
suspend fun delete(group: Group)
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Delete
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Update
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface StudentDao {
|
|
||||||
@Query("select * from students order by last_name collate nocase asc")
|
|
||||||
fun getAll(): Flow<List<Student>>
|
|
||||||
|
|
||||||
@Query("select * from students where students.uid = :uid")
|
|
||||||
fun getByUid(uid: Int): Flow<Student>
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
suspend fun insert(student: Student)
|
|
||||||
|
|
||||||
@Update
|
|
||||||
suspend fun update(student: Student)
|
|
||||||
|
|
||||||
@Delete
|
|
||||||
suspend fun delete(student: Student)
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface TaskDao {
|
||||||
|
@Query("select * from tasks order by name collate nocase asc")
|
||||||
|
fun getAll(): Flow<List<Task>>
|
||||||
|
|
||||||
|
@Query("select * from tasks where tasks.uid = :uid")
|
||||||
|
fun getByUid(uid: Int): Flow<Task>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(task: Task)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(task: Task)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(task: Task)
|
||||||
|
|
||||||
|
@Query("UPDATE tasks SET favorite = :favorite WHERE uid = :uid")
|
||||||
|
suspend fun updateFavorite(uid: Int, favorite: Boolean)
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.User
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserDao {
|
||||||
|
@Query("select * from users order by login collate nocase asc")
|
||||||
|
suspend fun getAll(): List<User>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(user: User)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(user: User)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun delete(user: User)
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.model
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Ignore
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "students", foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = Group::class,
|
|
||||||
parentColumns = ["uid"],
|
|
||||||
childColumns = ["group_id"],
|
|
||||||
onDelete = ForeignKey.RESTRICT,
|
|
||||||
onUpdate = ForeignKey.RESTRICT
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
data class Student(
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val uid: Int = 0,
|
|
||||||
@ColumnInfo(name = "first_name")
|
|
||||||
val firstName: String,
|
|
||||||
@ColumnInfo(name = "last_name")
|
|
||||||
val lastName: String,
|
|
||||||
@ColumnInfo(name = "group_id", index = true)
|
|
||||||
val groupId: Int,
|
|
||||||
val phone: String,
|
|
||||||
val email: String
|
|
||||||
) {
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
constructor(
|
|
||||||
firstName: String,
|
|
||||||
lastName: String,
|
|
||||||
group: Group,
|
|
||||||
phone: String,
|
|
||||||
email: String
|
|
||||||
) : this(0, firstName, lastName, group.uid, phone, email)
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getStudent(index: Int = 0): Student {
|
|
||||||
return Student(
|
|
||||||
index,
|
|
||||||
"first",
|
|
||||||
"last",
|
|
||||||
0,
|
|
||||||
"8 999 777 65 65",
|
|
||||||
"email@mail.ru"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
other as Student
|
|
||||||
if (uid != other.uid) return false
|
|
||||||
if (firstName != other.firstName) return false
|
|
||||||
if (lastName != other.lastName) return false
|
|
||||||
if (groupId != other.groupId) return false
|
|
||||||
if (phone != other.phone) return false
|
|
||||||
if (email != other.email) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = uid
|
|
||||||
result = 31 * result + firstName.hashCode()
|
|
||||||
result = 31 * result + lastName.hashCode()
|
|
||||||
result = 31 * result + groupId
|
|
||||||
result = 31 * result + phone.hashCode()
|
|
||||||
result = 31 * result + email.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,80 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.model
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "tasks", foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = User::class,
|
||||||
|
parentColumns = ["uid"],
|
||||||
|
childColumns = ["user_id"],
|
||||||
|
onDelete = ForeignKey.RESTRICT,
|
||||||
|
onUpdate = ForeignKey.RESTRICT
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Task(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val uid: Int = 0,
|
||||||
|
@ColumnInfo(name = "name")
|
||||||
|
val name: String,
|
||||||
|
@ColumnInfo(name = "description")
|
||||||
|
val description: String,
|
||||||
|
@ColumnInfo(name = "endDate")
|
||||||
|
val endDate: String,
|
||||||
|
@ColumnInfo(name = "favorite")
|
||||||
|
val favorite: Boolean,
|
||||||
|
@ColumnInfo(name = "user_id", index = true)
|
||||||
|
val userId: Int,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
constructor(
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
endDate: String,
|
||||||
|
favorite: Boolean,
|
||||||
|
user: User,
|
||||||
|
) : this(0, name, description, endDate, favorite, user.uid)
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getTask(index: Int = 0): Task {
|
||||||
|
return Task(
|
||||||
|
index,
|
||||||
|
"Test1234567",
|
||||||
|
"Test1235",
|
||||||
|
"11.12.2023",
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as Task
|
||||||
|
if (uid != other.uid) return false
|
||||||
|
if (name != other.name) return false
|
||||||
|
if (description != other.description) return false
|
||||||
|
if (endDate != other.endDate) return false
|
||||||
|
if (favorite != other.favorite) return false
|
||||||
|
if (userId != other.userId) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = uid
|
||||||
|
result = 31 * result + name.hashCode()
|
||||||
|
result = 31 * result + description.hashCode()
|
||||||
|
result = 31 * result + endDate.hashCode()
|
||||||
|
result = 31 * result + favorite.hashCode()
|
||||||
|
result = 31 * result + userId
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,22 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.model
|
package ru.ulstu.`is`.pmu.database.task.model
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
@Entity(tableName = "groups")
|
@Entity(tableName = "users")
|
||||||
data class Group(
|
data class User(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
val uid: Int = 0,
|
val uid: Int = 0,
|
||||||
@ColumnInfo(name = "group_name")
|
@ColumnInfo(name = "user")
|
||||||
val name: String
|
val name: String,
|
||||||
|
@ColumnInfo(name = "login")
|
||||||
|
val login: String
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
other as Group
|
other as User
|
||||||
if (uid != other.uid) return false
|
if (uid != other.uid) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -24,9 +26,10 @@ data class Group(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DEMO_GROUP = Group(
|
val DEMO_User = User(
|
||||||
0,
|
0,
|
||||||
"Группа 1"
|
"Sergey",
|
||||||
|
"Serega"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.repository
|
|
||||||
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Group
|
|
||||||
|
|
||||||
interface GroupRepository {
|
|
||||||
suspend fun getAllGroups(): List<Group>
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.repository
|
|
||||||
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.dao.GroupDao
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Group
|
|
||||||
|
|
||||||
class OfflineGroupRepository(private val groupDao: GroupDao) : GroupRepository {
|
|
||||||
override suspend fun getAllGroups(): List<Group> = groupDao.getAll()
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.repository
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.dao.StudentDao
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
|
||||||
|
|
||||||
class OfflineStudentRepository(private val studentDao: StudentDao) : StudentRepository {
|
|
||||||
override fun getAllStudents(): Flow<List<Student>> = studentDao.getAll()
|
|
||||||
|
|
||||||
override fun getStudent(uid: Int): Flow<Student?> = studentDao.getByUid(uid)
|
|
||||||
|
|
||||||
override suspend fun insertStudent(student: Student) = studentDao.insert(student)
|
|
||||||
|
|
||||||
override suspend fun updateStudent(student: Student) = studentDao.update(student)
|
|
||||||
|
|
||||||
override suspend fun deleteStudent(student: Student) = studentDao.delete(student)
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.dao.TaskDao
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
|
||||||
|
class OfflineTaskRepository(private val taskDao: TaskDao) : TaskRepository {
|
||||||
|
override fun getAllTasks(): Flow<List<Task>> = taskDao.getAll()
|
||||||
|
|
||||||
|
override fun getTask(uid: Int): Flow<Task?> = taskDao.getByUid(uid)
|
||||||
|
|
||||||
|
override suspend fun insertTask(task: Task) = taskDao.insert(task)
|
||||||
|
|
||||||
|
override suspend fun updateTask(task: Task) = taskDao.update(task)
|
||||||
|
|
||||||
|
override suspend fun deleteTask(task: Task) = taskDao.delete(task)
|
||||||
|
|
||||||
|
override suspend fun setTaskFavorite(uid: Int, favorite: Boolean) {
|
||||||
|
taskDao.updateFavorite(uid, favorite)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.repository
|
||||||
|
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.dao.UserDao
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.User
|
||||||
|
|
||||||
|
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
|
||||||
|
override suspend fun getAllUsers(): List<User> = userDao.getAll()
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.database.student.repository
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
|
||||||
|
|
||||||
interface StudentRepository {
|
|
||||||
fun getAllStudents(): Flow<List<Student>>
|
|
||||||
fun getStudent(uid: Int): Flow<Student?>
|
|
||||||
suspend fun insertStudent(student: Student)
|
|
||||||
suspend fun updateStudent(student: Student)
|
|
||||||
suspend fun deleteStudent(student: Student)
|
|
||||||
}
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
|
||||||
|
interface TaskRepository {
|
||||||
|
fun getAllTasks(): Flow<List<Task>>
|
||||||
|
fun getTask(uid: Int): Flow<Task?>
|
||||||
|
suspend fun insertTask(task: Task)
|
||||||
|
suspend fun updateTask(task: Task)
|
||||||
|
suspend fun deleteTask(task: Task)
|
||||||
|
suspend fun setTaskFavorite(uid: Int, favorite: Boolean = true)
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.database.task.repository
|
||||||
|
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.dao.UserDao
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.User
|
||||||
|
|
||||||
|
interface UserRepository {
|
||||||
|
suspend fun getAllUsers(): List<User>
|
||||||
|
}
|
@ -5,27 +5,27 @@ import androidx.lifecycle.createSavedStateHandle
|
|||||||
import androidx.lifecycle.viewmodel.CreationExtras
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import ru.ulstu.`is`.pmu.StudentApplication
|
import ru.ulstu.`is`.pmu.TaskApplication
|
||||||
import ru.ulstu.`is`.pmu.ui.student.edit.GroupDropDownViewModel
|
import ru.ulstu.`is`.pmu.ui.task.edit.UserDropDownViewModel
|
||||||
import ru.ulstu.`is`.pmu.ui.student.edit.StudentEditViewModel
|
import ru.ulstu.`is`.pmu.ui.task.edit.TaskEditViewModel
|
||||||
import ru.ulstu.`is`.pmu.ui.student.list.StudentListViewModel
|
import ru.ulstu.`is`.pmu.ui.task.list.TaskListViewModel
|
||||||
|
|
||||||
object AppViewModelProvider {
|
object AppViewModelProvider {
|
||||||
val Factory = viewModelFactory {
|
val Factory = viewModelFactory {
|
||||||
initializer {
|
initializer {
|
||||||
StudentListViewModel(studentApplication().container.studentRepository)
|
TaskListViewModel(taskApplication().container.taskRepository)
|
||||||
}
|
}
|
||||||
initializer {
|
initializer {
|
||||||
StudentEditViewModel(
|
TaskEditViewModel(
|
||||||
this.createSavedStateHandle(),
|
this.createSavedStateHandle(),
|
||||||
studentApplication().container.studentRepository
|
taskApplication().container.taskRepository
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
initializer {
|
initializer {
|
||||||
GroupDropDownViewModel(studentApplication().container.groupRepository)
|
UserDropDownViewModel(taskApplication().container.userRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CreationExtras.studentApplication(): StudentApplication =
|
fun CreationExtras.taskApplication(): TaskApplication =
|
||||||
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as StudentApplication)
|
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as TaskApplication)
|
@ -30,7 +30,7 @@ fun About() {
|
|||||||
|
|
||||||
val urlOnClick = {
|
val urlOnClick = {
|
||||||
val openURL = Intent(Intent.ACTION_VIEW)
|
val openURL = Intent(Intent.ACTION_VIEW)
|
||||||
openURL.data = Uri.parse("https://ulstu.ru/")
|
openURL.data = Uri.parse("https://vk.com/stranni20k")
|
||||||
localContext.startActivity(openURL)
|
localContext.startActivity(openURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ fun About() {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = urlOnClick
|
onClick = urlOnClick
|
||||||
) {
|
) {
|
||||||
Text(stringResource(id = R.string.about_title))
|
Text(stringResource(id = R.string.creator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,10 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import ru.ulstu.`is`.pmu.R
|
import ru.ulstu.`is`.pmu.R
|
||||||
import ru.ulstu.`is`.pmu.ui.about.About
|
import ru.ulstu.`is`.pmu.ui.about.About
|
||||||
import ru.ulstu.`is`.pmu.ui.student.edit.StudentEdit
|
import ru.ulstu.`is`.pmu.ui.task.edit.TaskEdit
|
||||||
import ru.ulstu.`is`.pmu.ui.student.list.StudentList
|
import ru.ulstu.`is`.pmu.ui.task.list.TaskList
|
||||||
|
import ru.ulstu.`is`.pmu.ui.task.list.TaskListEndDate
|
||||||
|
import ru.ulstu.`is`.pmu.ui.task.list.TaskListFavorite
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -98,16 +100,18 @@ fun Navhost(
|
|||||||
) {
|
) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController,
|
navController,
|
||||||
startDestination = Screen.StudentList.route,
|
startDestination = Screen.TaskList.route,
|
||||||
modifier.padding(innerPadding)
|
modifier.padding(innerPadding)
|
||||||
) {
|
) {
|
||||||
composable(Screen.StudentList.route) { StudentList(navController) }
|
composable(Screen.TaskList.route) { TaskList(navController) }
|
||||||
|
composable(Screen.TaskListEndDate.route) { TaskListEndDate(navController) }
|
||||||
|
composable(Screen.TaskListFavorite.route) { TaskListFavorite(navController) }
|
||||||
composable(Screen.About.route) { About() }
|
composable(Screen.About.route) { About() }
|
||||||
composable(
|
composable(
|
||||||
Screen.StudentEdit.route,
|
Screen.TaskEdit.route,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
) {
|
) {
|
||||||
StudentEdit(navController)
|
TaskEdit(navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ package ru.ulstu.`is`.pmu.ui.navigation
|
|||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.List
|
import androidx.compose.material.icons.filled.List
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import ru.ulstu.`is`.pmu.R
|
import ru.ulstu.`is`.pmu.R
|
||||||
|
|
||||||
@ -14,19 +16,27 @@ enum class Screen(
|
|||||||
val icon: ImageVector = Icons.Filled.Favorite,
|
val icon: ImageVector = Icons.Filled.Favorite,
|
||||||
val showInBottomBar: Boolean = true
|
val showInBottomBar: Boolean = true
|
||||||
) {
|
) {
|
||||||
StudentList(
|
TaskList(
|
||||||
"student-list", R.string.student_main_title, Icons.Filled.List
|
"task-list", R.string.task_main_title, Icons.Filled.List
|
||||||
|
),
|
||||||
|
TaskListEndDate(
|
||||||
|
"task-list-end-date", R.string.task_date_title, Icons.Filled.DateRange
|
||||||
|
),
|
||||||
|
TaskListFavorite(
|
||||||
|
"task-list-favorite", R.string.task_favorite, Icons.Filled.Favorite
|
||||||
),
|
),
|
||||||
About(
|
About(
|
||||||
"about", R.string.about_title, Icons.Filled.Info
|
"about", R.string.about_title, Icons.Filled.Info
|
||||||
),
|
),
|
||||||
StudentEdit(
|
TaskEdit(
|
||||||
"student-edit/{id}", R.string.student_view_title, showInBottomBar = false
|
"task-edit/{id}", R.string.task_view_title, showInBottomBar = false
|
||||||
);
|
);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val bottomBarItems = listOf(
|
val bottomBarItems = listOf(
|
||||||
StudentList,
|
TaskList,
|
||||||
|
TaskListEndDate,
|
||||||
|
TaskListFavorite,
|
||||||
About,
|
About,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.student.edit
|
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Group
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.GroupRepository
|
|
||||||
|
|
||||||
class GroupDropDownViewModel(
|
|
||||||
private val groupRepository: GroupRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
var groupsListUiState by mutableStateOf(GroupsListUiState())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var groupUiState by mutableStateOf(GroupUiState())
|
|
||||||
private set
|
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch {
|
|
||||||
groupsListUiState = GroupsListUiState(groupRepository.getAllGroups())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCurrentGroup(groupId: Int) {
|
|
||||||
val group: Group? =
|
|
||||||
groupsListUiState.groupList.firstOrNull { group -> group.uid == groupId }
|
|
||||||
group?.let { updateUiState(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateUiState(group: Group) {
|
|
||||||
groupUiState = GroupUiState(
|
|
||||||
group = group
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class GroupsListUiState(val groupList: List<Group> = listOf())
|
|
||||||
|
|
||||||
data class GroupUiState(
|
|
||||||
val group: Group? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Group.toUiState() = GroupUiState(group = Group(uid = uid, name = name))
|
|
@ -1,97 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.student.edit
|
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.StudentRepository
|
|
||||||
|
|
||||||
class StudentEditViewModel(
|
|
||||||
savedStateHandle: SavedStateHandle,
|
|
||||||
private val studentRepository: StudentRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
var studentUiState by mutableStateOf(StudentUiState())
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val studentUid: Int = checkNotNull(savedStateHandle["id"])
|
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch {
|
|
||||||
if (studentUid > 0) {
|
|
||||||
studentUiState = studentRepository.getStudent(studentUid)
|
|
||||||
.filterNotNull()
|
|
||||||
.first()
|
|
||||||
.toUiState(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateUiState(studentDetails: StudentDetails) {
|
|
||||||
studentUiState = StudentUiState(
|
|
||||||
studentDetails = studentDetails,
|
|
||||||
isEntryValid = validateInput(studentDetails)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun saveStudent() {
|
|
||||||
if (validateInput()) {
|
|
||||||
if (studentUid > 0) {
|
|
||||||
studentRepository.updateStudent(studentUiState.studentDetails.toStudent(studentUid))
|
|
||||||
} else {
|
|
||||||
studentRepository.insertStudent(studentUiState.studentDetails.toStudent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateInput(uiState: StudentDetails = studentUiState.studentDetails): Boolean {
|
|
||||||
return with(uiState) {
|
|
||||||
firstName.isNotBlank()
|
|
||||||
&& lastName.isNotBlank()
|
|
||||||
&& phone.isNotBlank()
|
|
||||||
&& email.isNotBlank()
|
|
||||||
&& groupId > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class StudentUiState(
|
|
||||||
val studentDetails: StudentDetails = StudentDetails(),
|
|
||||||
val isEntryValid: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
data class StudentDetails(
|
|
||||||
val firstName: String = "",
|
|
||||||
val lastName: String = "",
|
|
||||||
val phone: String = "",
|
|
||||||
val email: String = "",
|
|
||||||
val groupId: Int = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
fun StudentDetails.toStudent(uid: Int = 0): Student = Student(
|
|
||||||
uid = uid,
|
|
||||||
firstName = firstName,
|
|
||||||
lastName = lastName,
|
|
||||||
phone = phone,
|
|
||||||
email = email,
|
|
||||||
groupId = groupId
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Student.toDetails(): StudentDetails = StudentDetails(
|
|
||||||
firstName = firstName,
|
|
||||||
lastName = lastName,
|
|
||||||
phone = phone,
|
|
||||||
email = email,
|
|
||||||
groupId = groupId
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Student.toUiState(isEntryValid: Boolean = false): StudentUiState = StudentUiState(
|
|
||||||
studentDetails = this.toDetails(),
|
|
||||||
isEntryValid = isEntryValid
|
|
||||||
)
|
|
@ -1,11 +1,13 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.student.edit
|
package ru.ulstu.`is`.pmu.ui.task.edit
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@ -31,42 +33,43 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import java.util.Date
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.ulstu.`is`.pmu.R
|
import ru.ulstu.`is`.pmu.R
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Group
|
import ru.ulstu.`is`.pmu.database.task.model.User
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
|
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
|
||||||
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
|
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StudentEdit(
|
fun TaskEdit(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
viewModel: StudentEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
viewModel: TaskEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
groupViewModel: GroupDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
userViewModel: UserDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
groupViewModel.setCurrentGroup(viewModel.studentUiState.studentDetails.groupId)
|
userViewModel.setCurrentUser(viewModel.taskUiState.taskDetails.userId)
|
||||||
StudentEdit(
|
TaskEdit(
|
||||||
studentUiState = viewModel.studentUiState,
|
taskUiState = viewModel.taskUiState,
|
||||||
groupUiState = groupViewModel.groupUiState,
|
userUiState = userViewModel.userUiState,
|
||||||
groupsListUiState = groupViewModel.groupsListUiState,
|
usersListUiState = userViewModel.usersListUiState,
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
viewModel.saveStudent()
|
viewModel.saveTask()
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUpdate = viewModel::updateUiState,
|
onUpdate = viewModel::updateUiState,
|
||||||
onGroupUpdate = groupViewModel::updateUiState
|
onUserUpdate = userViewModel::updateUiState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun GroupDropDown(
|
private fun UserDropDown(
|
||||||
groupUiState: GroupUiState,
|
userUiState: UserUiState,
|
||||||
groupsListUiState: GroupsListUiState,
|
usersListUiState: UsersListUiState,
|
||||||
onGroupUpdate: (Group) -> Unit
|
onUserUpdate: (User) -> Unit
|
||||||
) {
|
) {
|
||||||
var expanded: Boolean by remember { mutableStateOf(false) }
|
var expanded: Boolean by remember { mutableStateOf(false) }
|
||||||
ExposedDropdownMenuBox(
|
ExposedDropdownMenuBox(
|
||||||
@ -78,8 +81,8 @@ private fun GroupDropDown(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
value = groupUiState.group?.name
|
value = userUiState.user?.name
|
||||||
?: stringResource(id = R.string.student_group_not_select),
|
?: stringResource(id = R.string.task_user_not_select),
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
@ -96,13 +99,13 @@ private fun GroupDropDown(
|
|||||||
.background(Color.White)
|
.background(Color.White)
|
||||||
.exposedDropdownSize()
|
.exposedDropdownSize()
|
||||||
) {
|
) {
|
||||||
groupsListUiState.groupList.forEach { group ->
|
usersListUiState.userList.forEach { user ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = {
|
text = {
|
||||||
Text(text = group.name)
|
Text(text = user.name)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
onGroupUpdate(group)
|
onUserUpdate(user)
|
||||||
expanded = false
|
expanded = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -113,14 +116,15 @@ private fun GroupDropDown(
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun StudentEdit(
|
private fun TaskEdit(
|
||||||
studentUiState: StudentUiState,
|
taskUiState: TaskUiState,
|
||||||
groupUiState: GroupUiState,
|
userUiState: UserUiState,
|
||||||
groupsListUiState: GroupsListUiState,
|
usersListUiState: UsersListUiState,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onUpdate: (StudentDetails) -> Unit,
|
onUpdate: (TaskDetails) -> Unit,
|
||||||
onGroupUpdate: (Group) -> Unit
|
onUserUpdate: (User) -> Unit
|
||||||
) {
|
) {
|
||||||
|
var showInvalidDateDialog by remember { mutableStateOf(false) }
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -128,68 +132,84 @@ private fun StudentEdit(
|
|||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
value = studentUiState.studentDetails.firstName,
|
value = taskUiState.taskDetails.name,
|
||||||
onValueChange = { onUpdate(studentUiState.studentDetails.copy(firstName = it)) },
|
onValueChange = { onUpdate(taskUiState.taskDetails.copy(name = it)) },
|
||||||
label = { Text(stringResource(id = R.string.student_firstname)) },
|
label = { Text(stringResource(id = R.string.task_firstname)) },
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
value = studentUiState.studentDetails.lastName,
|
value = taskUiState.taskDetails.description,
|
||||||
onValueChange = { onUpdate(studentUiState.studentDetails.copy(lastName = it)) },
|
onValueChange = { onUpdate(taskUiState.taskDetails.copy(description = it)) },
|
||||||
label = { Text(stringResource(id = R.string.student_lastname)) },
|
label = { Text(stringResource(id = R.string.task_lastname)) },
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
GroupDropDown(
|
|
||||||
groupUiState = groupUiState,
|
|
||||||
groupsListUiState = groupsListUiState,
|
|
||||||
onGroupUpdate = {
|
|
||||||
onUpdate(studentUiState.studentDetails.copy(groupId = it.uid))
|
|
||||||
onGroupUpdate(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
value = studentUiState.studentDetails.phone,
|
value = taskUiState.taskDetails.endDate,
|
||||||
onValueChange = { onUpdate(studentUiState.studentDetails.copy(phone = it)) },
|
onValueChange = { newDate -> onUpdate(taskUiState.taskDetails.copy(endDate = newDate)) },
|
||||||
label = { Text(stringResource(id = R.string.student_phone)) },
|
label = { Text(stringResource(id = R.string.task_endDate)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
|
||||||
)
|
|
||||||
OutlinedTextField(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
value = studentUiState.studentDetails.email,
|
|
||||||
onValueChange = { onUpdate(studentUiState.studentDetails.copy(email = it)) },
|
|
||||||
label = { Text(stringResource(id = R.string.student_email)) },
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
|
|
||||||
)
|
)
|
||||||
|
// UserDropDown(
|
||||||
|
// userUiState = userUiState,
|
||||||
|
// usersListUiState = usersListUiState,
|
||||||
|
// onUserUpdate = {
|
||||||
|
// onUpdate(taskUiState.taskDetails.copy(userId = it.uid))
|
||||||
|
// onUserUpdate(it)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
Button(
|
Button(
|
||||||
onClick = onClick,
|
onClick = {
|
||||||
enabled = studentUiState.isEntryValid,
|
if (!isValidDate(taskUiState.taskDetails.endDate)) {
|
||||||
|
showInvalidDateDialog = true
|
||||||
|
} else {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = taskUiState.isEntryValid,
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.student_save_button))
|
Text(text = stringResource(R.string.task_save_button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showInvalidDateDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showInvalidDateDialog = false },
|
||||||
|
title = { Text("Неверный формат даты") },
|
||||||
|
text = { Text("Введите дату по шаблону: 01.12.2023") },
|
||||||
|
confirmButton = {
|
||||||
|
Button(onClick = { showInvalidDateDialog = false }) {
|
||||||
|
Text("ОК")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isValidDate(date: String): Boolean {
|
||||||
|
val regex = Regex("""^\d{2}\.\d{2}\.\d{4}$""")
|
||||||
|
return regex.matches(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
fun StudentEditPreview() {
|
fun TaskEditPreview() {
|
||||||
PmudemoTheme {
|
PmudemoTheme {
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
StudentEdit(
|
TaskEdit(
|
||||||
studentUiState = Student.getStudent().toUiState(true),
|
taskUiState = Task.getTask().toUiState(true),
|
||||||
groupUiState = Group.DEMO_GROUP.toUiState(),
|
userUiState = User.DEMO_User.toUiState(),
|
||||||
groupsListUiState = GroupsListUiState(listOf()),
|
usersListUiState = UsersListUiState(listOf()),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onUpdate = {},
|
onUpdate = {},
|
||||||
onGroupUpdate = {}
|
onUserUpdate = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.ui.task.edit
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.repository.TaskRepository
|
||||||
|
|
||||||
|
class TaskEditViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val taskRepository: TaskRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
var taskUiState by mutableStateOf(TaskUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val taskUid: Int = checkNotNull(savedStateHandle["id"])
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (taskUid > 0) {
|
||||||
|
taskUiState = taskRepository.getTask(taskUid)
|
||||||
|
.filterNotNull()
|
||||||
|
.first()
|
||||||
|
.toUiState(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateUiState(taskDetails: TaskDetails) {
|
||||||
|
taskUiState = TaskUiState(
|
||||||
|
taskDetails = taskDetails,
|
||||||
|
isEntryValid = validateInput(taskDetails)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveTask() {
|
||||||
|
if (validateInput()) {
|
||||||
|
if (taskUid > 0) {
|
||||||
|
taskRepository.updateTask(taskUiState.taskDetails.toTask(taskUid))
|
||||||
|
} else {
|
||||||
|
taskRepository.insertTask(taskUiState.taskDetails.toTask())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateInput(uiState: TaskDetails = taskUiState.taskDetails): Boolean {
|
||||||
|
return with(uiState) {
|
||||||
|
name.isNotBlank()
|
||||||
|
&& description.isNotBlank()
|
||||||
|
&& endDate.isNotBlank()
|
||||||
|
&& userId > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TaskUiState(
|
||||||
|
val taskDetails: TaskDetails = TaskDetails(),
|
||||||
|
val isEntryValid: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TaskDetails(
|
||||||
|
val name: String = "",
|
||||||
|
val description: String = "",
|
||||||
|
val endDate: String = "",
|
||||||
|
val favorite: Boolean = false,
|
||||||
|
val userId: Int = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
fun TaskDetails.toTask(uid: Int = 0): Task = Task(
|
||||||
|
uid = uid,
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
endDate = endDate,
|
||||||
|
favorite = favorite,
|
||||||
|
userId = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Task.toDetails(): TaskDetails = TaskDetails(
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
endDate = endDate,
|
||||||
|
favorite = favorite,
|
||||||
|
userId = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Task.toUiState(isEntryValid: Boolean = false): TaskUiState = TaskUiState(
|
||||||
|
taskDetails = this.toDetails(),
|
||||||
|
isEntryValid = isEntryValid
|
||||||
|
)
|
@ -0,0 +1,46 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.ui.task.edit
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.User
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.repository.UserRepository
|
||||||
|
|
||||||
|
class UserDropDownViewModel(
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
var usersListUiState by mutableStateOf(UsersListUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var userUiState by mutableStateOf(UserUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
usersListUiState = UsersListUiState(userRepository.getAllUsers())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentUser(userId: Int) {
|
||||||
|
val user: User? =
|
||||||
|
usersListUiState.userList.firstOrNull { user -> user.uid == userId }
|
||||||
|
user?.let { updateUiState(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateUiState(user: User) {
|
||||||
|
userUiState = UserUiState(
|
||||||
|
user = user
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UsersListUiState(val userList: List<User> = listOf())
|
||||||
|
|
||||||
|
data class UserUiState(
|
||||||
|
val user: User? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
fun User.toUiState() = UserUiState(user = User(uid = uid, name = name, login = login))
|
@ -1,29 +0,0 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.student.list
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import ru.ulstu.`is`.pmu.database.AppDataContainer
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
|
||||||
import ru.ulstu.`is`.pmu.database.student.repository.StudentRepository
|
|
||||||
|
|
||||||
class StudentListViewModel(
|
|
||||||
private val studentRepository: StudentRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
val studentListUiState: StateFlow<StudentListUiState> = studentRepository.getAllStudents().map {
|
|
||||||
StudentListUiState(it)
|
|
||||||
}.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
|
|
||||||
initialValue = StudentListUiState()
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun deleteStudent(student: Student) {
|
|
||||||
studentRepository.deleteStudent(student)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class StudentListUiState(val studentList: List<Student> = listOf())
|
|
212
app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskList.kt
Normal file
212
app/src/main/java/ru/ulstu/is/pmu/ui/student/list/TaskList.kt
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.ui.task.list
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
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.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.DismissDirection
|
||||||
|
import androidx.compose.material3.DismissState
|
||||||
|
import androidx.compose.material3.DismissValue
|
||||||
|
import androidx.compose.material3.DismissValue.DismissedToStart
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.SwipeToDismiss
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberDismissState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.ulstu.`is`.pmu.R
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
|
||||||
|
import ru.ulstu.`is`.pmu.ui.navigation.Screen
|
||||||
|
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TaskList(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: TaskListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val taskListUiState by viewModel.taskListUiState.collectAsState()
|
||||||
|
Scaffold(
|
||||||
|
topBar = {},
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
val route = Screen.TaskEdit.route.replace("{id}", "0")
|
||||||
|
navController.navigate(route)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.Add, contentDescription = "Добавить")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
TaskListContent( // Изменил имя, чтобы избежать путаницы с функцией TaskList
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
taskList = taskListUiState.taskList,
|
||||||
|
onClick = { uid: Int ->
|
||||||
|
val route = Screen.TaskEdit.route.replace("{id}", uid.toString())
|
||||||
|
navController.navigate(route)
|
||||||
|
},
|
||||||
|
onSwipeLeft = { task: Task -> // Обработка свайпа влево (удаление)
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.deleteTask(task)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSwipeRight = { task: Task -> // Обработка свайпа вправо (добавить в избранное)
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.favoriteTask(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun SwipeToAction(
|
||||||
|
dismissState: DismissState,
|
||||||
|
task: Task,
|
||||||
|
onClick: (uid: Int) -> Unit,
|
||||||
|
onSwipeRight: (task: Task) -> Unit, // Для добавления в избранное
|
||||||
|
onSwipeLeft: (task: Task) -> Unit // Для удаления
|
||||||
|
) {
|
||||||
|
SwipeToDismiss(
|
||||||
|
state = dismissState,
|
||||||
|
directions = setOf(
|
||||||
|
DismissDirection.EndToStart, // Для удаления
|
||||||
|
DismissDirection.StartToEnd // Для добавления в избранное
|
||||||
|
),
|
||||||
|
background = {
|
||||||
|
val backgroundColor by animateColorAsState(
|
||||||
|
when (dismissState.targetValue) {
|
||||||
|
DismissedToStart -> Color.Red.copy(alpha = 0.8f)
|
||||||
|
DismissValue.DismissedToEnd -> Color.Green.copy(alpha = 0.8f) // Цвет фона для избранного
|
||||||
|
else -> Color.White
|
||||||
|
}, label = ""
|
||||||
|
)
|
||||||
|
val iconScale by animateFloatAsState(
|
||||||
|
targetValue = if (dismissState.targetValue == DismissedToStart || dismissState.targetValue == DismissValue.DismissedToEnd) 1.3f else 0.5f,
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = backgroundColor)
|
||||||
|
.padding(end = 16.dp, start = 16.dp),
|
||||||
|
contentAlignment = if (dismissState.targetValue == DismissedToStart) Alignment.CenterEnd else Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.scale(iconScale),
|
||||||
|
imageVector = if (dismissState.targetValue == DismissedToStart) Icons.Outlined.Delete else Icons.Default.Star, // Иконка для избранного
|
||||||
|
contentDescription = if (dismissState.targetValue == DismissedToStart) "Delete" else "Favorite",
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissContent = {
|
||||||
|
TaskListItem(task = task,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 7.dp)
|
||||||
|
.clickable { onClick(task.uid) })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TaskListContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
taskList: List<Task>,
|
||||||
|
onClick: (uid: Int) -> Unit,
|
||||||
|
onSwipeLeft: (task: Task) -> Unit,
|
||||||
|
onSwipeRight: (task: Task) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
if (taskList.isEmpty()) {
|
||||||
|
// Код для отображения пустого списка
|
||||||
|
} else {
|
||||||
|
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
|
||||||
|
items(items = taskList, key = { it.uid }) { task ->
|
||||||
|
val dismissState = rememberDismissState()
|
||||||
|
|
||||||
|
if (dismissState.isDismissed(DismissDirection.EndToStart)) {
|
||||||
|
LaunchedEffect(task) {
|
||||||
|
onSwipeLeft(task)
|
||||||
|
dismissState.reset() // Сбросить состояние после обработки свайпа
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dismissState.isDismissed(DismissDirection.StartToEnd)) {
|
||||||
|
LaunchedEffect(task) {
|
||||||
|
onSwipeRight(task)
|
||||||
|
dismissState.reset() // Сбросить состояние после обработки свайпа
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeToAction(
|
||||||
|
dismissState = dismissState,
|
||||||
|
task = task,
|
||||||
|
onClick = onClick,
|
||||||
|
onSwipeRight = onSwipeRight,
|
||||||
|
onSwipeLeft = onSwipeLeft
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TaskListItem(
|
||||||
|
task: Task, modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(all = 10.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = String.format("%s%n%s", task.name, task.description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.ui.task.list
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
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.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.DismissDirection
|
||||||
|
import androidx.compose.material3.DismissState
|
||||||
|
import androidx.compose.material3.DismissValue.DismissedToStart
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.SwipeToDismiss
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberDismissState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.ulstu.`is`.pmu.R
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
|
||||||
|
import ru.ulstu.`is`.pmu.ui.navigation.Screen
|
||||||
|
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TaskListEndDate(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: TaskListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
|
) {
|
||||||
|
val taskListEndDateUiState by viewModel.taskListEndDateUiState.collectAsState()
|
||||||
|
Scaffold(
|
||||||
|
topBar = {},
|
||||||
|
) { innerPadding ->
|
||||||
|
TaskListEndDate(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
taskList = taskListEndDateUiState.taskList,
|
||||||
|
onClick = { uid: Int ->
|
||||||
|
val route = Screen.TaskEdit.route.replace("{id}", uid.toString())
|
||||||
|
navController.navigate(route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseDate(dateString: String): Calendar? {
|
||||||
|
return try {
|
||||||
|
val formatter = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = formatter.parse(dateString)
|
||||||
|
calendar
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TaskListEndDate(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
taskList: List<Task>,
|
||||||
|
onClick: (uid: Int) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
if (taskList.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.task_empty_description),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
|
||||||
|
items(items = taskList.reversed(), key = { it.uid }) { task ->
|
||||||
|
val dismissState: DismissState = rememberDismissState(
|
||||||
|
positionalThreshold = { 200.dp.toPx() }
|
||||||
|
)
|
||||||
|
|
||||||
|
TaskListItem(task = task, modifier = Modifier
|
||||||
|
.padding(vertical = 7.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TaskListItem(
|
||||||
|
task: Task, modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(all = 10.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = String.format("%s%n%s%n%s", task.name, task.description, task.endDate)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ru.ulstu.`is`.pmu.ui.student.list
|
package ru.ulstu.`is`.pmu.ui.task.list
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
@ -45,43 +45,33 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.ulstu.`is`.pmu.R
|
import ru.ulstu.`is`.pmu.R
|
||||||
import ru.ulstu.`is`.pmu.database.student.model.Student
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
|
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
|
||||||
import ru.ulstu.`is`.pmu.ui.navigation.Screen
|
import ru.ulstu.`is`.pmu.ui.navigation.Screen
|
||||||
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
|
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StudentList(
|
fun TaskListFavorite(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
viewModel: StudentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
viewModel: TaskListViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val studentListUiState by viewModel.studentListUiState.collectAsState()
|
val taskListFavoriteUiState by viewModel.taskListFavoriteUiState.collectAsState()
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {},
|
topBar = {}
|
||||||
floatingActionButton = {
|
|
||||||
FloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
val route = Screen.StudentEdit.route.replace("{id}", 0.toString())
|
|
||||||
navController.navigate(route)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Icon(Icons.Filled.Add, "Добавить")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
StudentList(
|
TaskListFavorite(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
studentList = studentListUiState.studentList,
|
taskList = taskListFavoriteUiState.taskList,
|
||||||
onClick = { uid: Int ->
|
onClick = { uid: Int ->
|
||||||
val route = Screen.StudentEdit.route.replace("{id}", uid.toString())
|
val route = Screen.TaskEdit.route.replace("{id}", uid.toString())
|
||||||
navController.navigate(route)
|
navController.navigate(route)
|
||||||
},
|
},
|
||||||
onSwipe = { student: Student ->
|
onSwipe = { task: Task ->
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
viewModel.deleteStudent(student)
|
viewModel.deletefavoriteTask(task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -92,8 +82,7 @@ fun StudentList(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SwipeToDelete(
|
private fun SwipeToDelete(
|
||||||
dismissState: DismissState,
|
dismissState: DismissState,
|
||||||
student: Student,
|
task: Task
|
||||||
onClick: (uid: Int) -> Unit
|
|
||||||
) {
|
) {
|
||||||
SwipeToDismiss(
|
SwipeToDismiss(
|
||||||
state = dismissState,
|
state = dismissState,
|
||||||
@ -127,46 +116,44 @@ private fun SwipeToDelete(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissContent = {
|
dismissContent = {
|
||||||
StudentListItem(student = student,
|
TaskListItem(task = task,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 7.dp)
|
.padding(vertical = 7.dp))
|
||||||
.clickable { onClick(student.uid) })
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun StudentList(
|
private fun TaskListFavorite(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
studentList: List<Student>,
|
taskList: List<Task>,
|
||||||
onClick: (uid: Int) -> Unit,
|
onClick: (uid: Int) -> Unit,
|
||||||
onSwipe: (student: Student) -> Unit
|
onSwipe: (task: Task) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
if (studentList.isEmpty()) {
|
if (taskList.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.student_empty_description),
|
text = stringResource(R.string.task_empty_description),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.titleLarge
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
|
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
|
||||||
items(items = studentList, key = { it.uid }) { student ->
|
items(items = taskList, key = { it.uid }) { task ->
|
||||||
val dismissState: DismissState = rememberDismissState(
|
val dismissState: DismissState = rememberDismissState(
|
||||||
positionalThreshold = { 200.dp.toPx() }
|
positionalThreshold = { 200.dp.toPx() }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
|
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
|
||||||
onSwipe(student)
|
onSwipe(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
SwipeToDelete(
|
SwipeToDelete(
|
||||||
dismissState = dismissState,
|
dismissState = dismissState,
|
||||||
student = student,
|
task = task
|
||||||
onClick = onClick
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,8 +162,8 @@ private fun StudentList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StudentListItem(
|
private fun TaskListItem(
|
||||||
student: Student, modifier: Modifier = Modifier
|
task: Task, modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
@ -186,41 +173,7 @@ private fun StudentListItem(
|
|||||||
modifier = modifier.padding(all = 10.dp)
|
modifier = modifier.padding(all = 10.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = String.format("%s %s", student.firstName, student.lastName)
|
text = String.format("%s%n%s", task.name, task.description)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
|
||||||
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
fun StudentListPreview() {
|
|
||||||
PmudemoTheme {
|
|
||||||
Surface(
|
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
|
||||||
StudentList(
|
|
||||||
studentList = (1..20).map { i -> Student.getStudent(i) },
|
|
||||||
onClick = {},
|
|
||||||
onSwipe = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
|
||||||
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
fun StudentEmptyListPreview() {
|
|
||||||
PmudemoTheme {
|
|
||||||
Surface(
|
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
|
||||||
StudentList(
|
|
||||||
studentList = listOf(),
|
|
||||||
onClick = {},
|
|
||||||
onSwipe = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package ru.ulstu.`is`.pmu.ui.task.list
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import ru.ulstu.`is`.pmu.database.AppDataContainer
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.model.Task
|
||||||
|
import ru.ulstu.`is`.pmu.database.task.repository.TaskRepository
|
||||||
|
|
||||||
|
class TaskListViewModel(
|
||||||
|
private val taskRepository: TaskRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val taskListFavoriteUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks()
|
||||||
|
.map { tasks ->
|
||||||
|
val filteredTasks = tasks.filter { task ->
|
||||||
|
task.favorite // Оставить только задачи, у которых favorite = true
|
||||||
|
}
|
||||||
|
val sortedTasks = filteredTasks.sortedByDescending { task ->
|
||||||
|
parseDate(task.endDate)?.timeInMillis ?: Long.MIN_VALUE
|
||||||
|
}
|
||||||
|
TaskListUiState(sortedTasks)
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState())
|
||||||
|
|
||||||
|
// Для TaskListEndDate (Сортировка по endDate)
|
||||||
|
val taskListEndDateUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks()
|
||||||
|
.map { tasks ->
|
||||||
|
val sortedTasks = tasks.sortedByDescending { task ->
|
||||||
|
parseDate(task.endDate)?.timeInMillis ?: Long.MIN_VALUE
|
||||||
|
}
|
||||||
|
TaskListUiState(sortedTasks)
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState())
|
||||||
|
|
||||||
|
// Для TaskList (Сортировка по uid)
|
||||||
|
val taskListUiState: StateFlow<TaskListUiState> = taskRepository.getAllTasks()
|
||||||
|
.map { tasks ->
|
||||||
|
val sortedTasks = tasks.sortedBy { task ->
|
||||||
|
task.uid
|
||||||
|
}
|
||||||
|
TaskListUiState(sortedTasks)
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(AppDataContainer.TIMEOUT), TaskListUiState())
|
||||||
|
|
||||||
|
suspend fun deleteTask(task: Task) {
|
||||||
|
taskRepository.deleteTask(task)
|
||||||
|
}
|
||||||
|
suspend fun favoriteTask(task: Task) {
|
||||||
|
taskRepository.setTaskFavorite(task.uid, true)
|
||||||
|
}
|
||||||
|
suspend fun deletefavoriteTask(task: Task) {
|
||||||
|
taskRepository.setTaskFavorite(task.uid, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TaskListUiState(val taskList: List<Task> = listOf())
|
@ -1,19 +1,20 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">pmu-demo</string>
|
<string name="app_name">PMU-ToDoList</string>
|
||||||
<string name="student_firstname">Имя</string>
|
<string name="task_firstname">Название</string>
|
||||||
<string name="student_lastname">Фамилия</string>
|
<string name="task_lastname">Описание</string>
|
||||||
<string name="student_group">Группа</string>
|
<string name="task_main_title">Список задач</string>
|
||||||
<string name="student_phone">Телефон</string>
|
<string name="task_date_title">Ближайшие</string>
|
||||||
<string name="student_email">e-mail</string>
|
<string name="task_favorite">Избранные</string>
|
||||||
<string name="student_main_title">Список студентов</string>
|
<string name="task_endDate">Дата окончания задачи</string>
|
||||||
<string name="student_view_title">Профиль студента</string>
|
<string name="task_view_title">Задача</string>
|
||||||
<string name="student_empty_description">Записи о студентах отсутствуют</string>
|
<string name="task_empty_description">Задачи отсутствуют</string>
|
||||||
<string name="student_group_not_select">Группа не указана</string>
|
<string name="task_user_not_select">Пользователь не указан</string>
|
||||||
<string name="student_save_button">Сохранить</string>
|
<string name="task_save_button">Сохранить</string>
|
||||||
<string name="about_title">О нас</string>
|
<string name="about_title">О нас</string>
|
||||||
|
<string name="creator">Связаться с разработчиком</string>
|
||||||
<string name="about_text">
|
<string name="about_text">
|
||||||
<p>Это текст <b>о нас</b>!</p>\n\n
|
<p><b>Задачник</b> - ваш личный помощник в организации задач!</p>\n\n
|
||||||
<p>Здесь могла быть Ваша реклама!</p>\n\n
|
<p>Создавайте списки дел, отмечайте важные задачи и следите за своим прогрессом. Простой и интуитивно понятный интерфейс поможет вам организовать ваш день эффективно.</p>\n\n
|
||||||
<p>Наш сайт <a href="https://ulstu.ru">ulstu.ru</a></p>
|
<p>Планируйте, реализуйте и достигайте своих целей с нашим задачником</p>
|
||||||
</string>
|
</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue
Block a user