This commit is contained in:
Артём Батылкин 2023-12-12 02:03:27 +04:00
parent f68ea9109f
commit c5b54562d3
35 changed files with 940 additions and 563 deletions

View File

@ -66,6 +66,7 @@ dependencies {
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("com.google.firebase:protolite-well-known-types:18.0.0")
// Room
val room_version = "2.5.2"

View File

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".StudentApplication"
android:name=".TaskApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"

View File

@ -4,7 +4,7 @@ import android.app.Application
import ru.ulstu.`is`.pmu.database.AppContainer
import ru.ulstu.`is`.pmu.database.AppDataContainer
class StudentApplication : Application() {
class TaskApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {

View File

@ -1,22 +1,22 @@
package ru.ulstu.`is`.pmu.database
import android.content.Context
import ru.ulstu.`is`.pmu.database.student.repository.GroupRepository
import ru.ulstu.`is`.pmu.database.student.repository.OfflineGroupRepository
import ru.ulstu.`is`.pmu.database.student.repository.OfflineStudentRepository
import ru.ulstu.`is`.pmu.database.student.repository.StudentRepository
import ru.ulstu.`is`.pmu.database.task.repository.UserRepository
import ru.ulstu.`is`.pmu.database.task.repository.OfflineUserRepository
import ru.ulstu.`is`.pmu.database.task.repository.OfflineTaskRepository
import ru.ulstu.`is`.pmu.database.task.repository.TaskRepository
interface AppContainer {
val studentRepository: StudentRepository
val groupRepository: GroupRepository
val taskRepository: TaskRepository
val userRepository: UserRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val studentRepository: StudentRepository by lazy {
OfflineStudentRepository(AppDatabase.getInstance(context).studentDao())
override val taskRepository: TaskRepository by lazy {
OfflineTaskRepository(AppDatabase.getInstance(context).taskDao())
}
override val groupRepository: GroupRepository by lazy {
OfflineGroupRepository(AppDatabase.getInstance(context).groupDao())
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
companion object {

View File

@ -8,15 +8,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.database.student.dao.GroupDao
import ru.ulstu.`is`.pmu.database.student.dao.StudentDao
import ru.ulstu.`is`.pmu.database.student.model.Group
import ru.ulstu.`is`.pmu.database.student.model.Student
import ru.ulstu.`is`.pmu.database.task.dao.TaskDao
import ru.ulstu.`is`.pmu.database.task.dao.UserDao
import ru.ulstu.`is`.pmu.database.task.model.User
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 fun studentDao(): StudentDao
abstract fun groupDao(): GroupDao
abstract fun taskDao(): TaskDao
abstract fun userDao(): UserDao
companion object {
private const val DB_NAME: String = "pmy-db"
@ -26,26 +26,22 @@ abstract class AppDatabase : RoomDatabase() {
private suspend fun populateDatabase() {
INSTANCE?.let { database ->
// Groups
val groupDao = database.groupDao()
val group1 = Group(1, "Группа 1")
val group2 = Group(2, "Группа 2")
val group3 = Group(3, "Группа 3")
groupDao.insert(group1)
groupDao.insert(group2)
groupDao.insert(group3)
// Students
val studentDao = database.studentDao()
val student1 = Student("First1", "Last1", group1, "+79998887761", "st1@m.ru")
val student2 = Student("First2", "Last2", group2, "+79998887762", "st2@m.ru")
val student3 = Student("First3", "Last3", group3, "+79998887763", "st3@m.ru")
val student4 = Student("First4", "Last4", group3, "+79998887764", "st4@m.ru")
val student5 = Student("First5", "Last5", group2, "+79998887765", "st5@m.ru")
studentDao.insert(student1)
studentDao.insert(student2)
studentDao.insert(student3)
studentDao.insert(student4)
studentDao.insert(student5)
// Users
val userDao = database.userDao()
val user1 = User(1, "Sergey", "brook.sergey@gmail.com")
userDao.insert(user1)
// Tasks
val taskDao = database.taskDao()
val task1 = Task("First1", "Last1","12.12.2023",false, user1)
val task2 = Task("First2", "Last2","15.12.2023",false, user1)
val task3 = Task("First3", "Last3","10.12.2023",false, user1)
val task4 = Task("First4", "Last4","31.12.2023",false, user1)
val task5 = Task("First5", "Last5","05.12.2023",false, user1)
taskDao.insert(task1)
taskDao.insert(task2)
taskDao.insert(task3)
taskDao.insert(task4)
taskDao.insert(task5)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "groups")
data class Group(
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val uid: Int = 0,
@ColumnInfo(name = "group_name")
val name: String
@ColumnInfo(name = "user")
val name: String,
@ColumnInfo(name = "login")
val login: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Group
other as User
if (uid != other.uid) return false
return true
}
@ -24,9 +26,10 @@ data class Group(
}
companion object {
val DEMO_GROUP = Group(
val DEMO_User = User(
0,
"Группа 1"
"Sergey",
"Serega"
)
}
}

View File

@ -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>
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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>
}

View File

@ -5,27 +5,27 @@ import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import ru.ulstu.`is`.pmu.StudentApplication
import ru.ulstu.`is`.pmu.ui.student.edit.GroupDropDownViewModel
import ru.ulstu.`is`.pmu.ui.student.edit.StudentEditViewModel
import ru.ulstu.`is`.pmu.ui.student.list.StudentListViewModel
import ru.ulstu.`is`.pmu.TaskApplication
import ru.ulstu.`is`.pmu.ui.task.edit.UserDropDownViewModel
import ru.ulstu.`is`.pmu.ui.task.edit.TaskEditViewModel
import ru.ulstu.`is`.pmu.ui.task.list.TaskListViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
StudentListViewModel(studentApplication().container.studentRepository)
TaskListViewModel(taskApplication().container.taskRepository)
}
initializer {
StudentEditViewModel(
TaskEditViewModel(
this.createSavedStateHandle(),
studentApplication().container.studentRepository
taskApplication().container.taskRepository
)
}
initializer {
GroupDropDownViewModel(studentApplication().container.groupRepository)
UserDropDownViewModel(taskApplication().container.userRepository)
}
}
}
fun CreationExtras.studentApplication(): StudentApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as StudentApplication)
fun CreationExtras.taskApplication(): TaskApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as TaskApplication)

View File

@ -30,7 +30,7 @@ fun About() {
val urlOnClick = {
val openURL = Intent(Intent.ACTION_VIEW)
openURL.data = Uri.parse("https://ulstu.ru/")
openURL.data = Uri.parse("https://vk.com/stranni20k")
localContext.startActivity(openURL)
}
@ -47,7 +47,7 @@ fun About() {
modifier = Modifier.fillMaxWidth(),
onClick = urlOnClick
) {
Text(stringResource(id = R.string.about_title))
Text(stringResource(id = R.string.creator))
}
}
}

View File

@ -30,8 +30,10 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.ui.about.About
import ru.ulstu.`is`.pmu.ui.student.edit.StudentEdit
import ru.ulstu.`is`.pmu.ui.student.list.StudentList
import ru.ulstu.`is`.pmu.ui.task.edit.TaskEdit
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)
@Composable
@ -98,16 +100,18 @@ fun Navhost(
) {
NavHost(
navController,
startDestination = Screen.StudentList.route,
startDestination = Screen.TaskList.route,
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.StudentEdit.route,
Screen.TaskEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) {
StudentEdit(navController)
TaskEdit(navController)
}
}
}

View File

@ -2,9 +2,11 @@ package ru.ulstu.`is`.pmu.ui.navigation
import androidx.annotation.StringRes
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.Info
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.Person
import androidx.compose.ui.graphics.vector.ImageVector
import ru.ulstu.`is`.pmu.R
@ -14,19 +16,27 @@ enum class Screen(
val icon: ImageVector = Icons.Filled.Favorite,
val showInBottomBar: Boolean = true
) {
StudentList(
"student-list", R.string.student_main_title, Icons.Filled.List
TaskList(
"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", R.string.about_title, Icons.Filled.Info
),
StudentEdit(
"student-edit/{id}", R.string.student_view_title, showInBottomBar = false
TaskEdit(
"task-edit/{id}", R.string.task_view_title, showInBottomBar = false
);
companion object {
val bottomBarItems = listOf(
StudentList,
TaskList,
TaskListEndDate,
TaskListFavorite,
About,
)

View File

@ -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))

View File

@ -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
)

View File

@ -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 androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@ -31,42 +33,43 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import java.util.Date
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.database.student.model.Group
import ru.ulstu.`is`.pmu.database.student.model.Student
import ru.ulstu.`is`.pmu.database.task.model.User
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun StudentEdit(
fun TaskEdit(
navController: NavController,
viewModel: StudentEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
groupViewModel: GroupDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
viewModel: TaskEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
userViewModel: UserDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
groupViewModel.setCurrentGroup(viewModel.studentUiState.studentDetails.groupId)
StudentEdit(
studentUiState = viewModel.studentUiState,
groupUiState = groupViewModel.groupUiState,
groupsListUiState = groupViewModel.groupsListUiState,
userViewModel.setCurrentUser(viewModel.taskUiState.taskDetails.userId)
TaskEdit(
taskUiState = viewModel.taskUiState,
userUiState = userViewModel.userUiState,
usersListUiState = userViewModel.usersListUiState,
onClick = {
coroutineScope.launch {
viewModel.saveStudent()
viewModel.saveTask()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
onGroupUpdate = groupViewModel::updateUiState
onUserUpdate = userViewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun GroupDropDown(
groupUiState: GroupUiState,
groupsListUiState: GroupsListUiState,
onGroupUpdate: (Group) -> Unit
private fun UserDropDown(
userUiState: UserUiState,
usersListUiState: UsersListUiState,
onUserUpdate: (User) -> Unit
) {
var expanded: Boolean by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
@ -78,8 +81,8 @@ private fun GroupDropDown(
}
) {
TextField(
value = groupUiState.group?.name
?: stringResource(id = R.string.student_group_not_select),
value = userUiState.user?.name
?: stringResource(id = R.string.task_user_not_select),
onValueChange = {},
readOnly = true,
trailingIcon = {
@ -96,13 +99,13 @@ private fun GroupDropDown(
.background(Color.White)
.exposedDropdownSize()
) {
groupsListUiState.groupList.forEach { group ->
usersListUiState.userList.forEach { user ->
DropdownMenuItem(
text = {
Text(text = group.name)
Text(text = user.name)
},
onClick = {
onGroupUpdate(group)
onUserUpdate(user)
expanded = false
}
)
@ -113,14 +116,15 @@ private fun GroupDropDown(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun StudentEdit(
studentUiState: StudentUiState,
groupUiState: GroupUiState,
groupsListUiState: GroupsListUiState,
private fun TaskEdit(
taskUiState: TaskUiState,
userUiState: UserUiState,
usersListUiState: UsersListUiState,
onClick: () -> Unit,
onUpdate: (StudentDetails) -> Unit,
onGroupUpdate: (Group) -> Unit
onUpdate: (TaskDetails) -> Unit,
onUserUpdate: (User) -> Unit
) {
var showInvalidDateDialog by remember { mutableStateOf(false) }
Column(
Modifier
.fillMaxWidth()
@ -128,68 +132,84 @@ private fun StudentEdit(
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = studentUiState.studentDetails.firstName,
onValueChange = { onUpdate(studentUiState.studentDetails.copy(firstName = it)) },
label = { Text(stringResource(id = R.string.student_firstname)) },
value = taskUiState.taskDetails.name,
onValueChange = { onUpdate(taskUiState.taskDetails.copy(name = it)) },
label = { Text(stringResource(id = R.string.task_firstname)) },
singleLine = true
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = studentUiState.studentDetails.lastName,
onValueChange = { onUpdate(studentUiState.studentDetails.copy(lastName = it)) },
label = { Text(stringResource(id = R.string.student_lastname)) },
value = taskUiState.taskDetails.description,
onValueChange = { onUpdate(taskUiState.taskDetails.copy(description = it)) },
label = { Text(stringResource(id = R.string.task_lastname)) },
singleLine = true
)
GroupDropDown(
groupUiState = groupUiState,
groupsListUiState = groupsListUiState,
onGroupUpdate = {
onUpdate(studentUiState.studentDetails.copy(groupId = it.uid))
onGroupUpdate(it)
}
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = studentUiState.studentDetails.phone,
onValueChange = { onUpdate(studentUiState.studentDetails.copy(phone = it)) },
label = { Text(stringResource(id = R.string.student_phone)) },
value = taskUiState.taskDetails.endDate,
onValueChange = { newDate -> onUpdate(taskUiState.taskDetails.copy(endDate = newDate)) },
label = { Text(stringResource(id = R.string.task_endDate)) },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
)
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)
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
// UserDropDown(
// userUiState = userUiState,
// usersListUiState = usersListUiState,
// onUserUpdate = {
// onUpdate(taskUiState.taskDetails.copy(userId = it.uid))
// onUserUpdate(it)
// }
// )
Button(
onClick = onClick,
enabled = studentUiState.isEntryValid,
onClick = {
if (!isValidDate(taskUiState.taskDetails.endDate)) {
showInvalidDateDialog = true
} else {
onClick()
}
},
enabled = taskUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
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 = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun StudentEditPreview() {
fun TaskEditPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
StudentEdit(
studentUiState = Student.getStudent().toUiState(true),
groupUiState = Group.DEMO_GROUP.toUiState(),
groupsListUiState = GroupsListUiState(listOf()),
TaskEdit(
taskUiState = Task.getTask().toUiState(true),
userUiState = User.DEMO_User.toUiState(),
usersListUiState = UsersListUiState(listOf()),
onClick = {},
onUpdate = {},
onGroupUpdate = {}
onUserUpdate = {}
)
}
}

View File

@ -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
)

View File

@ -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))

View File

@ -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())

View 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)
)
}
}
}

View File

@ -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)
)
}
}
}

View File

@ -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 androidx.compose.animation.animateColorAsState
@ -45,43 +45,33 @@ 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.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.navigation.Screen
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun StudentList(
fun TaskListFavorite(
navController: NavController,
viewModel: StudentListViewModel = viewModel(factory = AppViewModelProvider.Factory)
viewModel: TaskListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val studentListUiState by viewModel.studentListUiState.collectAsState()
val taskListFavoriteUiState by viewModel.taskListFavoriteUiState.collectAsState()
Scaffold(
topBar = {},
floatingActionButton = {
FloatingActionButton(
onClick = {
val route = Screen.StudentEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
},
) {
Icon(Icons.Filled.Add, "Добавить")
}
}
topBar = {}
) { innerPadding ->
StudentList(
TaskListFavorite(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
studentList = studentListUiState.studentList,
taskList = taskListFavoriteUiState.taskList,
onClick = { uid: Int ->
val route = Screen.StudentEdit.route.replace("{id}", uid.toString())
val route = Screen.TaskEdit.route.replace("{id}", uid.toString())
navController.navigate(route)
},
onSwipe = { student: Student ->
onSwipe = { task: Task ->
coroutineScope.launch {
viewModel.deleteStudent(student)
viewModel.deletefavoriteTask(task)
}
}
)
@ -92,8 +82,7 @@ fun StudentList(
@Composable
private fun SwipeToDelete(
dismissState: DismissState,
student: Student,
onClick: (uid: Int) -> Unit
task: Task
) {
SwipeToDismiss(
state = dismissState,
@ -127,46 +116,44 @@ private fun SwipeToDelete(
}
},
dismissContent = {
StudentListItem(student = student,
TaskListItem(task = task,
modifier = Modifier
.padding(vertical = 7.dp)
.clickable { onClick(student.uid) })
.padding(vertical = 7.dp))
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun StudentList(
private fun TaskListFavorite(
modifier: Modifier = Modifier,
studentList: List<Student>,
taskList: List<Task>,
onClick: (uid: Int) -> Unit,
onSwipe: (student: Student) -> Unit
onSwipe: (task: Task) -> Unit
) {
Column(
modifier = modifier
) {
if (studentList.isEmpty()) {
if (taskList.isEmpty()) {
Text(
text = stringResource(R.string.student_empty_description),
text = stringResource(R.string.task_empty_description),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
} else {
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(
positionalThreshold = { 200.dp.toPx() }
)
if (dismissState.isDismissed(direction = DismissDirection.EndToStart)) {
onSwipe(student)
onSwipe(task)
}
SwipeToDelete(
dismissState = dismissState,
student = student,
onClick = onClick
task = task
)
}
}
@ -175,8 +162,8 @@ private fun StudentList(
}
@Composable
private fun StudentListItem(
student: Student, modifier: Modifier = Modifier
private fun TaskListItem(
task: Task, modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
@ -186,41 +173,7 @@ private fun StudentListItem(
modifier = modifier.padding(all = 10.dp)
) {
Text(
text = String.format("%s %s", student.firstName, student.lastName)
)
}
}
}
@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 = {}
text = String.format("%s%n%s", task.name, task.description)
)
}
}

View File

@ -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())

View File

@ -1,19 +1,20 @@
<resources>
<string name="app_name">pmu-demo</string>
<string name="student_firstname">Имя</string>
<string name="student_lastname">Фамилия</string>
<string name="student_group">Группа</string>
<string name="student_phone">Телефон</string>
<string name="student_email">e-mail</string>
<string name="student_main_title">Список студентов</string>
<string name="student_view_title">Профиль студента</string>
<string name="student_empty_description">Записи о студентах отсутствуют</string>
<string name="student_group_not_select">Группа не указана</string>
<string name="student_save_button">Сохранить</string>
<string name="app_name">PMU-ToDoList</string>
<string name="task_firstname">Название</string>
<string name="task_lastname">Описание</string>
<string name="task_main_title">Список задач</string>
<string name="task_date_title">Ближайшие</string>
<string name="task_favorite">Избранные</string>
<string name="task_endDate">Дата окончания задачи</string>
<string name="task_view_title">Задача</string>
<string name="task_empty_description">Задачи отсутствуют</string>
<string name="task_user_not_select">Пользователь не указан</string>
<string name="task_save_button">Сохранить</string>
<string name="about_title">О нас</string>
<string name="creator">Связаться с разработчиком</string>
<string name="about_text">
<p>Это текст <b>о нас</b>!</p>\n\n
<p>Здесь могла быть Ваша реклама!</p>\n\n
<p>Наш сайт <a href="https://ulstu.ru">ulstu.ru</a></p>
<p><b>Задачник</b> - ваш личный помощник в организации задач!</p>\n\n
<p>Создавайте списки дел, отмечайте важные задачи и следите за своим прогрессом. Простой и интуитивно понятный интерфейс поможет вам организовать ваш день эффективно.</p>\n\n
<p>Планируйте, реализуйте и достигайте своих целей с нашим задачником</p>
</string>
</resources>