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-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"

View File

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

View File

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

View File

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

View File

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

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.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"
) )
} }
} }

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

View File

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

View File

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

View File

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

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 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 = {}
) )
} }
} }

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 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 = {}
) )
} }
} }

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