This commit is contained in:
Stranni15k 2024-01-08 21:59:29 +04:00
parent 71f82033ec
commit e4a08474a5
28 changed files with 1647 additions and 168 deletions

View File

@ -1,4 +1,3 @@
package ru.ulstu.is.pmu.api
package ru.ulstu.`is`.pmu.api
class ApiStatus {
}
enum class ApiStatus { LOADING, ERROR, DONE }

View File

@ -21,9 +21,16 @@ import retrofit2.http.Path
import retrofit2.http.Query
import ru.ulstu.`is`.pmu.api.model.UserRemote
import ru.ulstu.`is`.pmu.api.model.TaskRemote
import ru.ulstu.`is`.pmu.api.report.ReportRemote
interface MyServerService {
@GET("report")
suspend fun getReportInfo(
@Query("fromDate") fromDate: String,
@Query("toDate") toDate: String
): List<ReportRemote>
@GET("users")
suspend fun getUsers(): List<UserRemote>

View File

@ -9,8 +9,8 @@ data class TaskRemote(
val name: String = "",
val description: String = "",
val endDate: String = "",
val favorite: Boolean,
val userId: Int,
val favorite: Boolean = false, // Значение по умолчанию
val userId: Int = 1, // Значение по умолчанию
)
fun TaskRemote.toTask(): Task = Task(
@ -18,7 +18,7 @@ fun TaskRemote.toTask(): Task = Task(
name,
description,
endDate,
favorite = false,
favorite,
userId
)
@ -27,6 +27,6 @@ fun Task.toTaskRemote(): TaskRemote = TaskRemote(
name,
description,
endDate,
favorite = false,
favorite,
userId = 1
)

View File

@ -1,4 +1,11 @@
package ru.ulstu.is.pmu.api.report
package ru.ulstu.`is`.pmu.api.report
class ReportRemote {
}
import kotlinx.serialization.Serializable
@Serializable
data class ReportRemote(
val id: Int = 0,
val name: String = "",
val description: String = "",
val endDate: String = ""
)

View File

@ -10,12 +10,16 @@ import ru.ulstu.`is`.pmu.api.MyServerService
import ru.ulstu.`is`.pmu.api.user.RestUserRepository
import ru.ulstu.`is`.pmu.api.model.toTask
import ru.ulstu.`is`.pmu.api.model.toTaskRemote
import ru.ulstu.`is`.pmu.api.report.ReportRemote
import ru.ulstu.`is`.pmu.common.AppContainer
import ru.ulstu.`is`.pmu.common.TaskRepository
import ru.ulstu.`is`.pmu.database.AppDatabase
import ru.ulstu.`is`.pmu.database.remotekeys.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.database.task.repository.OfflineTaskRepository
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class RestTaskRepository(
private val service: MyServerService,
@ -46,6 +50,50 @@ class RestTaskRepository(
).flow
}
override fun getAllFavoriteTasks(): Flow<PagingData<Task>> {
Log.d(RestTaskRepository::class.simpleName, "Get tasks")
val pagingSourceFactory = { dbTaskRepository.getAllTasksFavoritePagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = TaskRemoteMediator(
service,
dbTaskRepository,
dbRemoteKeyRepository,
userRestRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override fun getAllDateTasks(): Flow<PagingData<Task>> {
Log.d(RestTaskRepository::class.simpleName, "Get tasks")
val pagingSourceFactory = { dbTaskRepository.getAllTasksDatePagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = TaskRemoteMediator(
service,
dbTaskRepository,
dbRemoteKeyRepository,
userRestRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun getTask(uid: Int): Task =
service.getTask(uid).toTask()
@ -60,4 +108,19 @@ class RestTaskRepository(
override suspend fun deleteTask(task: Task) {
service.deleteTask(task.uid).toTask()
}
override suspend fun favoriteTask(task: Task) {
task.favorite = true
service.updateTask(task.uid, task.toTaskRemote()).toTask()
}
override suspend fun deletefavoriteTask(task: Task) {
task.favorite = false
service.updateTask(task.uid, task.toTaskRemote()).toTask()
}
suspend fun getReport(fromDate: String, toDate: String):List<ReportRemote>
{
return service.getReportInfo(fromDate,toDate)
}
}

View File

@ -6,8 +6,11 @@ import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import ru.ulstu.`is`.pmu.TaskApplication
import ru.ulstu.`is`.pmu.ui.ReportViewModel
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.FavoriteTaskList
import ru.ulstu.`is`.pmu.ui.task.list.FavoriteTaskListViewModel
import ru.ulstu.`is`.pmu.ui.task.list.TaskListViewModel
object AppViewModelProvider {
@ -24,6 +27,14 @@ object AppViewModelProvider {
initializer {
UserDropDownViewModel(taskApplication().container.userRestRepository)
}
initializer {
FavoriteTaskListViewModel(taskApplication().container.taskRestRepository)
}
initializer {
ReportViewModel(
taskApplication().container.taskRestRepository,
)
}
}
}

View File

@ -6,8 +6,12 @@ import ru.ulstu.`is`.pmu.database.task.model.Task
interface TaskRepository {
fun getAllTasks(): Flow<PagingData<Task>>
fun getAllFavoriteTasks(): Flow<PagingData<Task>>
fun getAllDateTasks(): Flow<PagingData<Task>>
suspend fun getTask(uid: Int): Task
suspend fun insertTask(task: Task)
suspend fun updateTask(task: Task)
suspend fun deleteTask(task: Task)
suspend fun favoriteTask(task: Task)
suspend fun deletefavoriteTask(task: Task)
}

View File

@ -5,9 +5,11 @@ import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.database.task.model.User
enum class RemoteKeyType(private val type: String) {
STUDENT(Task::class.simpleName ?: "Task");
STUDENT(Task::class.simpleName ?: "Task"),
USER(User::class.simpleName ?: "User");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }

View File

@ -14,6 +14,12 @@ interface TaskDao {
@Query("select * from tasks order by name collate nocase asc")
fun getAll(): PagingSource<Int, Task>
@Query("SELECT * FROM tasks WHERE favorite = 1")
fun getFavoriteTasks(): PagingSource<Int, Task>
@Query("SELECT * FROM tasks ORDER BY DATE(SUBSTR(endDate, 7, 4) || '-' || SUBSTR(endDate, 4, 2) || '-' || SUBSTR(endDate, 1, 2)) ASC")
fun getTasksSortedByDate(): PagingSource<Int, Task>
@Query("select * from tasks where tasks.uid = :uid")
fun getByUid(uid: Int): Flow<Task>
@ -26,6 +32,9 @@ interface TaskDao {
@Delete
suspend fun delete(task: Task)
@Query("UPDATE tasks SET favorite = :favorite WHERE uid = :taskId")
suspend fun updateFavorite(taskId: Int, favorite: Boolean)
@Query("DELETE FROM tasks")
suspend fun deleteAll()
}

View File

@ -1,23 +1,27 @@
package ru.ulstu.`is`.pmu.database.task.dao
import androidx.paging.PagingSource
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.Task
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)
suspend fun insert(vararg user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("DELETE FROM users")
suspend fun deleteAll()
}

View File

@ -39,7 +39,7 @@ data class Task(
endDate: String,
favorite: Boolean,
user: User,
) : this(0, name, description, endDate, favorite, user.uid)
) : this(0, name, description, endDate, favorite, 1)
companion object {

View File

@ -20,6 +20,22 @@ class OfflineTaskRepository(private val taskDao: TaskDao) : TaskRepository {
pagingSourceFactory = taskDao::getAll
).flow
override fun getAllFavoriteTasks(): Flow<PagingData<Task>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = taskDao::getFavoriteTasks
).flow
override fun getAllDateTasks(): Flow<PagingData<Task>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = taskDao::getTasksSortedByDate
).flow
override suspend fun getTask(uid: Int): Task = taskDao.getByUid(uid).first()
override suspend fun insertTask(task: Task) = taskDao.insert(task)
@ -27,12 +43,21 @@ class OfflineTaskRepository(private val taskDao: TaskDao) : TaskRepository {
override suspend fun updateTask(task: Task) = taskDao.update(task)
override suspend fun deleteTask(task: Task) = taskDao.delete(task)
override suspend fun favoriteTask(task: Task) {
TODO("Not yet implemented")
taskDao.updateFavorite(task.uid, !task.favorite)
}
override suspend fun deletefavoriteTask(task: Task) {
taskDao.updateFavorite(task.uid, !task.favorite)
}
fun getAllTasksPagingSource(): PagingSource<Int, Task> = taskDao.getAll()
fun getAllTasksFavoritePagingSource(): PagingSource<Int, Task> = taskDao.getFavoriteTasks()
fun getAllTasksDatePagingSource(): PagingSource<Int, Task> = taskDao.getTasksSortedByDate()
suspend fun insertTasks(tasks: List<Task>) =
taskDao.insert(*tasks.toTypedArray())

View File

@ -1,11 +1,23 @@
package ru.ulstu.`is`.pmu.database.task.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.common.AppContainer
import ru.ulstu.`is`.pmu.common.UserRepository
import ru.ulstu.`is`.pmu.database.task.dao.UserDao
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.database.task.model.User
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override suspend fun getAllUsers(): List<User> = userDao.getAll()
suspend fun insertUsers(users: List<User>) =
userDao.insert(*users.toTypedArray())
suspend fun createUser(user: User) = userDao.insert(user)
suspend fun updateUser(user: User) = userDao.update(user)
suspend fun clearUsers() = userDao.deleteAll()
}

View File

@ -1,4 +1,362 @@
package ru.ulstu.is.pmu.ui
package ru.ulstu.`is`.pmu.ui
class ReportPage {
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.pdf.PdfDocument
import android.net.Uri
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.widget.CalendarView
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerState
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.api.report.ReportRemote
import ru.ulstu.`is`.pmu.common.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.ReportViewModel
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReportPage(navController: NavController?, viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val coroutineScope = rememberCoroutineScope()
var showStartDatePicker by remember { mutableStateOf(false) }
var showEndDatePicker by remember { mutableStateOf(false) }
val context = LocalContext.current
Column(
modifier = Modifier
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Button(
onClick = { showStartDatePicker = true },
modifier = Modifier.width(350.dp)
) {
Text("Начальная дата")
}
}
if (showStartDatePicker) {
DatePicker(
onDateSelected = { selectedDate ->
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = selectedDate))
showStartDatePicker = false
},
onDismissRequest = { showStartDatePicker = false }
)
}
Button(
onClick = { showEndDatePicker = true },
modifier = Modifier.width(350.dp)
) {
Text("Конечная дата")
}
if (showEndDatePicker) {
DatePicker(
onDateSelected = { selectedDate ->
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = selectedDate))
showEndDatePicker = false
if (viewModel.reportPageUiState.reportDetails.startDate != null) {
coroutineScope.launch {
viewModel.getReport()
createPdfFile(context, "Отчет.pdf", viewModel.reportResultPageUiState.resReport)
}
}
},
onDismissRequest = { showEndDatePicker = false }
)
}
Spacer(modifier = Modifier.height(32.dp))
Text(
text = "Результат",
style = MaterialTheme.typography.headlineLarge
)
ReportTable(reportData = viewModel.reportResultPageUiState.resReport)
}
}
@Composable
fun ReportTable(reportData: List<ReportRemote>) {
Column(modifier = Modifier.padding(16.dp)) {
reportData.forEach { report ->
ReportCard(report)
}
}
}
@Composable
fun ReportCard(report: ReportRemote) {
val (taskId, taskName, taskDescription, taskEndDate) = report
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) // Исправлено здесь
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("ID: ${taskId}", style = MaterialTheme.typography.bodyMedium)
Text("Название: $taskName", style = MaterialTheme.typography.bodyMedium)
Text("Описание: $taskDescription", style = MaterialTheme.typography.bodyMedium)
Text("Дата завершения: $taskEndDate", style = MaterialTheme.typography.bodyMedium)
}
}
}
@Composable
fun CustomCalendarView(onDateSelected: (Date) -> Unit) {
AndroidView(
modifier = Modifier.wrapContentSize(),
factory = { context ->
val calendarView = CalendarView(context)
// Настройки CalendarView
val calendar = Calendar.getInstance()
calendarView.setOnDateChangeListener { _, year, month, dayOfMonth ->
calendar.set(Calendar.YEAR, year)
calendar.set(Calendar.MONTH, month)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
onDateSelected(calendar.time)
}
calendarView
}
)
}
@Composable
fun DatePicker(onDateSelected: (Date) -> Unit, onDismissRequest: () -> Unit) {
val selDate = remember { mutableStateOf(Date()) }
//todo - add strings to resource after POC
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = Color.White
)
) {
Column(
Modifier
.defaultMinSize(minHeight = 72.dp)
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.primary
)
.padding(16.dp)
) {
Text(
text = "Выберите дату"
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()).format(selDate.value)
)
Spacer(modifier = Modifier.size(16.dp))
}
CustomCalendarView(onDateSelected = {
selDate.value = it
})
Spacer(modifier = Modifier.size(8.dp))
Row(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = 16.dp, end = 16.dp)
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
) {
TextButton(
onClick = onDismissRequest
) {
//TODO - hardcode string
Text(
text = "Отмена"
)
}
TextButton(
onClick = {
onDateSelected(selDate.value)
onDismissRequest()
}
) {
//TODO - hardcode string
Text(
text = "Подтвердить"
)
}
}
}
}
}
suspend fun createPdfFile(context: Context, fileName: String, reportData: List<ReportRemote>) {
withContext(Dispatchers.IO) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS)
}
val contentResolver: ContentResolver = context.contentResolver
val uri: Uri? = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues)
uri?.let {
try {
contentResolver.openOutputStream(uri)?.use { outputStream ->
createPdfContent(outputStream, reportData)
}
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri))
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
private fun createPdfContent(outputStream: OutputStream, reportData: List<ReportRemote>) {
val pdfDocument = PdfDocument()
val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create()
val page = pdfDocument.startPage(pageInfo)
val canvas = page.canvas
val paint = Paint()
paint.color = android.graphics.Color.BLACK
paint.textSize = 24f
// Отображаем заголовок
val title = "Отчет"
val xTitle = (pageInfo.pageWidth - paint.measureText(title)) / 2
val yTitle = 40f
canvas.drawText(title, xTitle, yTitle, paint)
// Определение структуры таблицы
val tableWidth = pageInfo.pageWidth - 80f
val columnCount = 4
val columnWidth = tableWidth / columnCount
val rowHeight = 40f
// Отображаем заголовки столбцов
val columnHeaderPaint = Paint()
columnHeaderPaint.color = android.graphics.Color.BLACK
columnHeaderPaint.textSize = 18f
val columnHeaderTitles = listOf("ID", "Название", "Описание", "Дата окончания")
for (i in 0 until columnCount) {
val x = 40f + i * columnWidth
val y = yTitle + 80f
canvas.drawText(columnHeaderTitles[i], x, y, columnHeaderPaint)
}
// Отображаем данные в таблице
val rowDataPaint = Paint()
rowDataPaint.color = android.graphics.Color.BLACK
rowDataPaint.textSize = 18f
for ((index, report) in reportData.withIndex()) {
val x = 40f
val y = yTitle + 120f + index * rowHeight
// Отображаем данные каждого столбца
canvas.drawText(report.id.toString(), x, y, rowDataPaint)
canvas.drawText(report.name, x + columnWidth, y, rowDataPaint)
canvas.drawText(report.description, x + 2 * columnWidth, y, rowDataPaint)
canvas.drawText(report.endDate, x + 3 * columnWidth, y, rowDataPaint)
}
pdfDocument.finishPage(page)
pdfDocument.writeTo(outputStream)
pdfDocument.close()
}
private fun drawCell(canvas: Canvas, paint: Paint, text: String, x: Float, y: Float, width: Int) {
canvas.drawText(text, x + (width - paint.measureText(text)) / 2, y + paint.textSize, paint)
}

View File

@ -9,8 +9,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.api.model.TaskRemote
import ru.ulstu.`is`.pmu.api.model.toTask
import ru.ulstu.`is`.pmu.api.report.ReportRemote
import ru.ulstu.`is`.pmu.api.task.RestTaskRepository
import ru.ulstu.`is`.pmu.database.task.model.Task
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

View File

@ -29,8 +29,10 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.ui.ReportPage
import ru.ulstu.`is`.pmu.ui.about.About
import ru.ulstu.`is`.pmu.ui.task.edit.TaskEdit
import ru.ulstu.`is`.pmu.ui.task.list.FavoriteTaskList
import ru.ulstu.`is`.pmu.ui.task.list.TaskList
@OptIn(ExperimentalMaterial3Api::class)
@ -109,6 +111,8 @@ fun Navhost(
) {
TaskEdit(navController)
}
composable(Screen.TaskFavoriteList.route) { FavoriteTaskList(navController) }
composable(Screen.Report.route) { ReportPage(navController = navController) }
}
}

View File

@ -5,6 +5,7 @@ import androidx.compose.material.icons.Icons
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.Send
import androidx.compose.ui.graphics.vector.ImageVector
import ru.ulstu.`is`.pmu.R
@ -20,6 +21,12 @@ enum class Screen(
About(
"about", R.string.about_title, Icons.Filled.Info
),
TaskFavoriteList(
"task-favorite", R.string.task_favorite_view_title
),
Report(
"report", R.string.report, Icons.Filled.Send
),
TaskEdit(
"task-edit/{id}", R.string.task_view_title, showInBottomBar = false
);
@ -27,7 +34,9 @@ enum class Screen(
companion object {
val bottomBarItems = listOf(
TaskList,
TaskFavoriteList,
About,
Report
)
fun getItem(route: String): Screen? {

View File

@ -1,11 +1,19 @@
package ru.ulstu.`is`.pmu.ui.task.edit
import android.content.res.Configuration
import android.widget.CalendarView
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
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
@ -16,6 +24,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -23,12 +32,16 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.launch
@ -37,6 +50,10 @@ import ru.ulstu.`is`.pmu.database.task.model.User
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.common.AppViewModelProvider
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
@Composable
fun TaskEdit(
@ -44,17 +61,14 @@ fun TaskEdit(
viewModel: TaskEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
userViewModel: UserDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
userViewModel.setCurrentUser(viewModel.taskUiState.taskDetails.userId)
TaskEdit(
taskUiState = viewModel.taskUiState,
userUiState = userViewModel.userUiState,
usersListUiState = userViewModel.usersListUiState,
onClick = {
coroutineScope.launch {
viewModel.saveTask()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
onUserUpdate = userViewModel::updateUiState
@ -121,6 +135,7 @@ private fun TaskEdit(
onUpdate: (TaskDetails) -> Unit,
onUserUpdate: (User) -> Unit
) {
var showInvalidDateDialog by remember { mutableStateOf(false) }
Column(
Modifier
.fillMaxWidth()
@ -140,48 +155,166 @@ private fun TaskEdit(
label = { Text(stringResource(id = R.string.task_lastname)) },
singleLine = true
)
UserDropDown(
userUiState = userUiState,
usersListUiState = usersListUiState,
onUserUpdate = {
onUpdate(taskUiState.taskDetails.copy(userId = it.uid))
onUserUpdate(it)
var showDatePicker by remember { mutableStateOf(false) }
if (showDatePicker) {
DatePicker(
onDateSelected = { selectedDate ->
onUpdate(taskUiState.taskDetails.copy(endDate = SimpleDateFormat("dd.MM.yyyy").format(selectedDate)))
showDatePicker = false
},
onDismissRequest = {
showDatePicker = false
}
)
}
Button(
onClick = { showDatePicker = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Выбрать дату окончания")
}
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = taskUiState.taskDetails.endDate,
onValueChange = { onUpdate(taskUiState.taskDetails.copy(endDate = it)) },
label = { Text(stringResource(id = R.string.task_phone)) },
singleLine = true
singleLine = true ,
enabled = false
)
Button(
onClick = onClick,
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.task_save_button))
}
}
}
@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 TaskEditPreview() {
PmudemoTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
TaskEdit(
taskUiState = Task.getTask().toUiState(true),
userUiState = User.DEMO_User.toUiState(),
usersListUiState = UsersListUiState(listOf()),
onClick = {},
onUpdate = {},
onUserUpdate = {}
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)
}
@Composable
fun CustomCalendarView(onDateSelected: (Date) -> Unit) {
AndroidView(
modifier = Modifier.wrapContentSize(),
factory = { context ->
val calendarView = CalendarView(context)
// Настройки CalendarView
val calendar = Calendar.getInstance()
// Устанавливаем минимальную дату выбора на сегодняшний день
calendarView.minDate = calendar.timeInMillis
calendarView.setOnDateChangeListener { _, year, month, dayOfMonth ->
calendar.set(Calendar.YEAR, year)
calendar.set(Calendar.MONTH, month)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
onDateSelected(calendar.time)
}
calendarView
}
)
}
@Composable
fun DatePicker(onDateSelected: (Date) -> Unit, onDismissRequest: () -> Unit) {
val selDate = remember { mutableStateOf(Date()) }
//todo - add strings to resource after POC
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = Color.White
)
) {
Column(
Modifier
.defaultMinSize(minHeight = 72.dp)
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.primary
)
.padding(16.dp)
) {
Text(
text = "Выберите дату"
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()).format(selDate.value)
)
Spacer(modifier = Modifier.size(16.dp))
}
CustomCalendarView(onDateSelected = {
selDate.value = it
})
Spacer(modifier = Modifier.size(8.dp))
Row(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = 16.dp, end = 16.dp)
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
) {
TextButton(
onClick = onDismissRequest
) {
//TODO - hardcode string
Text(
text = "Отмена"
)
}
TextButton(
onClick = {
onDateSelected(selDate.value)
onDismissRequest()
}
) {
//TODO - hardcode string
Text(
text = "Подтвердить"
)
}
}
}
}
}

View File

@ -7,13 +7,14 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.common.MyViewModel
import ru.ulstu.`is`.pmu.common.TaskRepository
import ru.ulstu.`is`.pmu.database.task.model.Task
class TaskEditViewModel(
savedStateHandle: SavedStateHandle,
private val taskRepository: TaskRepository
) : ViewModel() {
) : MyViewModel() {
var taskUiState by mutableStateOf(TaskUiState())
private set
@ -21,11 +22,16 @@ class TaskEditViewModel(
private val taskUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (taskUid > 0) {
runInScope(
actionSuccess = {
taskUiState = taskRepository.getTask(taskUid)
.toUiState(true)
},
actionError = {
taskUiState = TaskUiState()
}
)
}
}
@ -35,10 +41,9 @@ class TaskEditViewModel(
isEntryValid = validateInput(taskDetails)
)
}
suspend fun saveTask() {
fun saveTask() {
if (validateInput()) {
if (taskUid > 0) {
runInScope ( actionSuccess = { if (taskUid > 0) {
taskRepository.updateTask(
taskUiState.taskDetails.toTask(taskUid)
)
@ -47,6 +52,7 @@ class TaskEditViewModel(
taskUiState.taskDetails.toTask()
)
}
} )
}
}
@ -55,7 +61,6 @@ class TaskEditViewModel(
name.isNotBlank()
&& description.isNotBlank()
&& endDate.isNotBlank()
&& userId > 0
}
}
}

View File

@ -1,4 +1,176 @@
package ru.ulstu.is.pmu.ui.student.list
package ru.ulstu.`is`.pmu.ui.task.list
class FavoriteTaskList {
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.material.ExperimentalMaterialApi
import androidx.compose.material.IconButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
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.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.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.common.AppViewModelProvider
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.ui.navigation.Screen
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun FavoriteTaskList(
navController: NavController,
viewModel: FavoriteTaskListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val taskListUiState = viewModel.taskListUiState.collectAsLazyPagingItems()
Scaffold(
topBar = {}
) { innerPadding ->
FavoriteTaskList(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
taskList = taskListUiState
)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun SwipeToDelete(
task: Task
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 7.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = String.format("%s%n%s%n%s", task.name, task.description, task.endDate),
modifier = Modifier.weight(1f)
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun FavoriteTaskList(
modifier: Modifier = Modifier,
taskList: LazyPagingItems<Task>
) {
val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
fun refresh() = refreshScope.launch {
refreshing = true
taskList.refresh()
refreshing = false
}
val state = rememberPullRefreshState(refreshing, ::refresh)
Box(
modifier = modifier.pullRefresh(state)
) {
Column(
modifier = modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.padding(all = 10.dp)) {
items(
count = taskList.itemCount,
key = taskList.itemKey(),
contentType = taskList.itemContentType()
) { index ->
val task = taskList[index]
task?.let {
SwipeToDelete(
task = task
)
}
}
}
PullRefreshIndicator(
refreshing, state,
Modifier
.align(CenterHorizontally)
.zIndex(100f)
)
}
}
}
@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 %s", task.name, task.description)
)
}
}
}

View File

@ -1,4 +1,15 @@
package ru.ulstu.is.pmu.ui.student.list
package ru.ulstu.`is`.pmu.ui.task.list
class FavoriteTaskListViewModel {
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.common.MyViewModel
import ru.ulstu.`is`.pmu.common.TaskRepository
import ru.ulstu.`is`.pmu.database.task.model.Task
class FavoriteTaskListViewModel(
private val taskRepository: TaskRepository
) : MyViewModel() {
val taskListUiState: Flow<PagingData<Task>> = taskRepository.getAllFavoriteTasks()
}

View File

@ -16,11 +16,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.IconButton
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
@ -66,13 +68,20 @@ import ru.ulstu.`is`.pmu.common.AppViewModelProvider
import ru.ulstu.`is`.pmu.database.task.model.Task
import ru.ulstu.`is`.pmu.ui.navigation.Screen
import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
import androidx.compose.material3.AlertDialog
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
@Composable
fun TaskList(
navController: NavController,
viewModel: TaskListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val taskListUiState = viewModel.taskListUiState.collectAsLazyPagingItems()
Scaffold(
topBar = {},
@ -92,21 +101,15 @@ fun TaskList(
.padding(innerPadding)
.fillMaxSize(),
taskList = taskListUiState,
onClick = { uid: Int ->
val route = Screen.TaskEdit.route.replace("{id}", uid.toString())
navController.navigate(route)
},
onDeleteClick = { task: Task ->
// Обработка удаления задачи
coroutineScope.launch {
viewModel.deleteTask(task)
}
taskListUiState.refresh()
},
onAddToFavoritesClick = { task: Task ->
// Обработка добавления задачи в избранное
coroutineScope.launch {
viewModel.favoriteTask(task)
}
if(task.favorite == false) { viewModel.favoriteTask(task) } else viewModel.deletefavoriteTask(task)
taskListUiState.refresh()
},
onEditClick = { task: Task ->
// Обработка редактирования задачи
@ -145,15 +148,66 @@ fun DismissBackground(dismissState: DismissState) {
}
}
fun calculateDaysDifference(endDate: Date, currentDate: Date): Int {
try {
val timeDifference = endDate.time - currentDate.time
return (timeDifference / (24 * 60 * 60 * 1000)).toInt()
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun SwipeToDelete(
task: Task,
onClick: (uid: Int) -> Unit,
onDeleteClick: (task: Task) -> Unit,
onAddToFavoritesClick: (task: Task) -> Unit,
onEditClick: (task: Task) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
// Добавляем диалоговое окно для подтверждения удаления задачи
if (showDialog) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.5f)) // Меняем уровень прозрачности и цвет
) {
// Диалоговое окно
AlertDialog(
onDismissRequest = { showDialog = false },
title = {
Text(text = "Удаление задачи")
},
text = {
Text(text = "Вы уверены, что хотите удалить эту задачу?")
},
confirmButton = {
TextButton(
onClick = {
onDeleteClick(task)
showDialog =
false // Закрываем диалоговое окно после подтверждения удаления
}
) {
Text(text = "Да")
}
},
dismissButton = {
TextButton(
onClick = {
showDialog = false // Закрываем диалоговое окно без удаления задачи
}
) {
Text(text = "Отмена")
}
}
)
}
}
Card(
modifier = Modifier
.fillMaxWidth()
@ -165,8 +219,35 @@ private fun SwipeToDelete(
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
val currentDateString = SimpleDateFormat("dd.MM.yyyy").format(Date())
val currentDate = SimpleDateFormat("dd.MM.yyyy").parse(currentDateString)
val taskName = task.name
val taskDescription = task.description
val endDate = SimpleDateFormat("dd.MM.yyyy").parse(task.endDate)
val isOverdue = currentDate.after(endDate)
val overdueText = if (isOverdue) {
val daysDifference = calculateDaysDifference(currentDate, endDate)
val daysWord = if (daysDifference < 23) "дня" else "дней"
"Задача просрочена на $daysDifference $daysWord"
} else {
task.endDate
}
Text(
text = String.format("%s %s", task.name, task.description),
text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Black)) {
append("$taskName\n$taskDescription\n")
}
if (isOverdue) {
withStyle(style = SpanStyle(color = Color.Red)) {
append(overdueText)
}
} else {
append(overdueText)
}
},
modifier = Modifier.weight(1f)
)
@ -177,18 +258,18 @@ private fun SwipeToDelete(
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "Редактировать",
tint = Color.Blue
tint = Color.Black
)
}
IconButton(
onClick = { onDeleteClick(task) },
onClick = { showDialog = true }, // Показываем диалоговое окно при нажатии на кнопку удаления
modifier = Modifier.padding(start = 8.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить",
tint = Color.Red
tint = Color.Black
)
}
@ -197,8 +278,9 @@ private fun SwipeToDelete(
modifier = Modifier.padding(start = 8.dp)
) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = "Добавить в избранное"
imageVector = if (task.favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
contentDescription = "Добавить в избранное",
tint = Color.Black
)
}
}
@ -210,7 +292,6 @@ private fun SwipeToDelete(
private fun TaskList(
modifier: Modifier = Modifier,
taskList: LazyPagingItems<Task>,
onClick: (uid: Int) -> Unit,
onDeleteClick: (task: Task) -> Unit,
onAddToFavoritesClick: (task: Task) -> Unit,
onEditClick: (task: Task) -> Unit
@ -240,7 +321,6 @@ private fun TaskList(
task?.let {
SwipeToDelete(
task = task,
onClick = onClick,
onDeleteClick = onDeleteClick,
onAddToFavoritesClick = onAddToFavoritesClick,
onEditClick = onEditClick
@ -257,23 +337,3 @@ private fun TaskList(
}
}
}
@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 %s", task.name, task.description)
)
}
}
}

View File

@ -3,17 +3,25 @@ package ru.ulstu.`is`.pmu.ui.task.list
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.common.MyViewModel
import ru.ulstu.`is`.pmu.common.TaskRepository
import ru.ulstu.`is`.pmu.database.task.model.Task
class TaskListViewModel(
private val taskRepository: TaskRepository
) : ViewModel() {
) : MyViewModel() {
val taskListUiState: Flow<PagingData<Task>> = taskRepository.getAllTasks()
val taskListUiState: Flow<PagingData<Task>> = taskRepository.getAllDateTasks()
suspend fun deleteTask(task: Task) {
taskRepository.deleteTask(task)
fun deleteTask(task: Task) {
runInScope ( actionSuccess = {taskRepository.deleteTask(task)})
}
fun favoriteTask(task: Task) {
runInScope ( actionSuccess = {taskRepository.favoriteTask(task)})
}
fun deletefavoriteTask(task: Task) {
runInScope ( actionSuccess = {taskRepository.deletefavoriteTask(task)})
}
}

View File

@ -1,16 +1,22 @@
<resources>
<string name="app_name">pmu-demo</string>
<string name="task_firstname">Имя</string>
<string name="task_lastname">Фамилия</string>
<string name="task_user">Группа</string>
<string name="task_phone">Телефон</string>
<string name="task_firstname">Название задачи</string>
<string name="task_lastname">Описание задачи</string>
<string name="task_user">Пользователь</string>
<string name="task_phone">Дата окончания</string>
<string name="task_email">e-mail</string>
<string name="task_main_title">Список студентов</string>
<string name="task_main_title">Список задач</string>
<string name="task_view_title">Профиль студента</string>
<string name="task_favorite_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="back">Назад</string>
<string name="loading">Загрузка…</string>
<string name="report">Отчет</string>
<string name="startDate">Дата начала</string>
<string name="endDate">Дата конца</string>
<string name="about_text">
<p>Это текст <b>о нас</b>!</p>\n\n
<p>Здесь могла быть Ваша реклама!</p>\n\n

42
server/.gitignore vendored
View File

@ -1,42 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@ -14,11 +14,15 @@
"tasks": [
{
"id": 1,
"name": "123456",
"description": "32155",
"endDate": "55",
"favorite": false,
"userId": 1
"name": "dsads",
"description": "12312312",
"endDate": "02.11.2023"
},
{
"id": 3,
"name": "test",
"description": "test",
"endDate": "05.12.2023"
}
]
}

629
server/package-lock.json generated
View File

@ -7,10 +7,26 @@
"": {
"name": "fake-db",
"version": "1.0.0",
"dependencies": {
"pdfkit": "^0.14.0"
},
"devDependencies": {
"json-server": "0.17.4"
}
},
"node_modules/@swc/helpers": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz",
"integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@swc/helpers/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -48,12 +64,54 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/array-buffer-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
"integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
"dependencies": {
"call-bind": "^1.0.2",
"is-array-buffer": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"dev": true
},
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@ -90,6 +148,14 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/brotli": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
"dependencies": {
"base64-js": "^1.1.2"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -103,7 +169,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.1",
@ -143,6 +208,14 @@
"node": ">=12"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -278,6 +351,11 @@
"node": ">= 0.10"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -287,11 +365,46 @@
"ms": "2.0.0"
}
},
"node_modules/deep-equal": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
"integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
"dependencies": {
"array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.5",
"es-get-iterator": "^1.1.3",
"get-intrinsic": "^1.2.2",
"is-arguments": "^1.1.1",
"is-array-buffer": "^3.0.2",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.2",
"isarray": "^2.0.5",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"object.assign": "^4.1.4",
"regexp.prototype.flags": "^1.5.1",
"side-channel": "^1.0.4",
"which-boxed-primitive": "^1.0.2",
"which-collection": "^1.0.1",
"which-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-equal/node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
},
"node_modules/define-data-property": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
@ -301,6 +414,22 @@
"node": ">= 0.4"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -320,6 +449,11 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dfa": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -354,6 +488,30 @@
"node": ">= 0.8"
}
},
"node_modules/es-get-iterator": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
"integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.3",
"has-symbols": "^1.0.3",
"is-arguments": "^1.1.1",
"is-map": "^2.0.2",
"is-set": "^2.0.2",
"is-string": "^1.0.7",
"isarray": "^2.0.5",
"stop-iteration-iterator": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-get-iterator/node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -516,6 +674,30 @@
"node": ">= 0.8"
}
},
"node_modules/fontkit": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz",
"integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==",
"dependencies": {
"@swc/helpers": "^0.3.13",
"brotli": "^1.3.2",
"clone": "^2.1.2",
"deep-equal": "^2.0.5",
"dfa": "^1.2.0",
"restructure": "^2.0.1",
"tiny-inflate": "^1.0.3",
"unicode-properties": "^1.3.1",
"unicode-trie": "^2.0.0"
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"dependencies": {
"is-callable": "^1.1.3"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -538,7 +720,14 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -556,7 +745,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
@ -571,7 +759,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3"
},
@ -585,6 +772,14 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -598,7 +793,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.2"
},
@ -610,7 +804,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@ -622,7 +815,20 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dependencies": {
"has-symbols": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
@ -634,7 +840,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@ -676,6 +881,19 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/internal-slot": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
"integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
"dependencies": {
"get-intrinsic": "^1.2.2",
"hasown": "^2.0.0",
"side-channel": "^1.0.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -685,6 +903,85 @@
"node": ">= 0.10"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
"integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.0",
"is-typed-array": "^1.1.10"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"dependencies": {
"has-bigints": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@ -694,12 +991,130 @@
"node": ">=8"
}
},
"node_modules/is-map": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
"integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number-object": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
"dev": true
},
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-set": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
"integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-shared-array-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
"dependencies": {
"call-bind": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-string": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-symbol": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
"dependencies": {
"has-symbols": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-typed-array": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
"dependencies": {
"which-typed-array": "^1.1.11"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-weakmap": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
"integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-weakset": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
"integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@ -754,6 +1169,23 @@
"node": ">=12"
}
},
"node_modules/linebreak": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
"dependencies": {
"base64-js": "0.0.8",
"unicode-trie": "^2.0.0"
}
},
"node_modules/linebreak/node_modules/base64-js": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -940,7 +1372,46 @@
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/object.assign": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
"integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==",
"dependencies": {
"call-bind": "^1.0.5",
"define-properties": "^1.2.1",
"has-symbols": "^1.0.3",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -981,6 +1452,17 @@
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
"dev": true
},
"node_modules/pdfkit": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.14.0.tgz",
"integrity": "sha512-Hnor8/78jhHm6ONrxWhrqOwAVALlBnFyWOF8sstBZMiqHZgZ5A6RU+Q3yahhw82plxpT7LOfH3b3qcOX6rzMQg==",
"dependencies": {
"crypto-js": "^4.2.0",
"fontkit": "^1.8.1",
"linebreak": "^1.0.2",
"png-js": "^1.0.0"
}
},
"node_modules/pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
@ -1008,6 +1490,11 @@
"node": ">=4"
}
},
"node_modules/png-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
"integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -1060,6 +1547,22 @@
"node": ">= 0.8"
}
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
"integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"set-function-name": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -1069,6 +1572,11 @@
"node": ">=0.10.0"
}
},
"node_modules/restructure": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz",
"integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg=="
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@ -1142,7 +1650,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
"dev": true,
"dependencies": {
"define-data-property": "^1.1.1",
"get-intrinsic": "^1.2.1",
@ -1153,6 +1660,19 @@
"node": ">= 0.4"
}
},
"node_modules/set-function-name": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
"integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
"dependencies": {
"define-data-property": "^1.0.1",
"functions-have-names": "^1.2.3",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -1163,7 +1683,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
@ -1191,6 +1710,17 @@
"graceful-fs": "^4.1.3"
}
},
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
"integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
"dependencies": {
"internal-slot": "^1.0.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@ -1229,6 +1759,11 @@
"node": ">=8"
}
},
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1251,6 +1786,29 @@
"node": ">= 0.6"
}
},
"node_modules/unicode-properties": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
"dependencies": {
"base64-js": "^1.3.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/unicode-trie": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
"dependencies": {
"pako": "^0.2.5",
"tiny-inflate": "^1.0.0"
}
},
"node_modules/unicode-trie/node_modules/pako": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -1278,6 +1836,53 @@
"node": ">= 0.8"
}
},
"node_modules/which-boxed-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
"dependencies": {
"is-bigint": "^1.0.1",
"is-boolean-object": "^1.1.0",
"is-number-object": "^1.0.4",
"is-string": "^1.0.5",
"is-symbol": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/which-collection": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
"integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
"dependencies": {
"is-map": "^2.0.1",
"is-set": "^2.0.1",
"is-weakmap": "^2.0.1",
"is-weakset": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/which-typed-array": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
"integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
"dependencies": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.4",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

View File

@ -2,9 +2,10 @@
"name": "fake-db",
"version": "1.0.0",
"scripts": {
"start": "json-server --watch data.json --host 0.0.0.0 -p 8079"
"start": "json-server --watch data.json --middlewares ./server.js --host 0.0.0.0 -p 8079"
},
"dependencies": {
"pdfkit": "^0.14.0"
},
"devDependencies": {
"json-server": "0.17.4"