Add additional lessons feature (#1550)

This commit is contained in:
Mateusz Idziejczak
2021-12-21 00:36:59 +01:00
committed by GitHub
parent cc22985dc5
commit 094df212b4
30 changed files with 3222 additions and 91 deletions

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.db
import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@ -146,6 +147,9 @@ import javax.inject.Singleton
Notification::class,
AdminMessage::class
],
autoMigrations = [
AutoMigration(from = 44, to = 45)
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
)
@ -153,7 +157,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 44
const val VERSION_SCHEMA = 45
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@ -198,7 +202,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration41(sharedPrefProvider),
Migration42(),
Migration43(),
Migration44()
Migration44(),
)
fun newInstance(

View File

@ -5,6 +5,7 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
import java.util.UUID
import javax.inject.Singleton
@Dao
@ -12,5 +13,13 @@ import javax.inject.Singleton
interface TimetableAdditionalDao : BaseDao<TimetableAdditional> {
@Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<TimetableAdditional>>
fun loadAll(
diaryId: Int,
studentId: Int,
from: LocalDate,
end: LocalDate
): Flow<List<TimetableAdditional>>
@Query("DELETE FROM TimetableAdditional WHERE repeat_id = :repeatId")
suspend fun deleteAllByRepeatId(repeatId: UUID)
}

View File

@ -42,7 +42,6 @@ data class Semester(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_current")
var current: Boolean = false
}

View File

@ -6,6 +6,7 @@ import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.UUID
@Entity(tableName = "TimetableAdditional")
data class TimetableAdditional(
@ -27,4 +28,10 @@ data class TimetableAdditional(
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "repeat_id", defaultValue = "NULL")
var repeatId: UUID? = null
@ColumnInfo(name = "is_added_by_user", defaultValue = "0")
var isAddedByUser: Boolean = false
}

View File

@ -152,7 +152,8 @@ class TimetableRepository @Inject constructor(
old: List<TimetableAdditional>,
new: List<TimetableAdditional>
) {
timetableAdditionalDb.deleteAll(old uniqueSubtract new)
val oldFiltered = old.filter { !it.isAddedByUser }
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
timetableAdditionalDb.insertAll(new uniqueSubtract old)
}
@ -160,4 +161,14 @@ class TimetableRepository @Inject constructor(
timetableHeaderDb.deleteAll(old uniqueSubtract new)
timetableHeaderDb.insertAll(new uniqueSubtract old)
}
suspend fun saveAdditionalList(additionalList: List<TimetableAdditional>) =
timetableAdditionalDb.insertAll(additionalList)
suspend fun deleteAdditional(additional: TimetableAdditional, deleteSeries: Boolean) =
if (deleteSeries) {
timetableAdditionalDb.deleteAllByRepeatId(additional.repeatId!!)
} else {
timetableAdditionalDb.deleteAll(listOf(additional))
}
}

View File

@ -29,8 +29,8 @@ import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.schoolYearStart
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import java.time.LocalDate
@ -225,7 +225,7 @@ class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.frag
}
override fun showDatePickerDialog(currentDate: LocalDate) {
val baseDate = currentDate.schoolYearStart
val baseDate = currentDate.firstSchoolDayInSchoolYear
val rangeStart = baseDate.toTimestamp()
val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp()

View File

@ -11,6 +11,8 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogHomeworkAddBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
@ -98,14 +100,17 @@ class HomeworkAddDialog : BaseDialogFragment<DialogHomeworkAddBinding>(), Homewo
}
override fun showDatePickerDialog(currentDate: LocalDate) {
val rangeStart = LocalDate.now().toTimestamp()
val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp()
val constraintsBuilder = CalendarConstraints.Builder().apply {
setStart(LocalDate.now().toEpochDay())
setStart(rangeStart)
setEnd(rangeEnd)
setValidator(SchoolDaysValidator(rangeStart, rangeEnd))
}
val datePicker =
MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
datePicker.addOnPositiveButtonClickListener {
date = it.toLocalDateTime().toLocalDate()

View File

@ -16,7 +16,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.schoolYearStart
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import java.time.LocalDate
@ -112,7 +112,7 @@ class LuckyNumberHistoryFragment :
}
override fun showDatePickerDialog(currentDate: LocalDate) {
val baseDate = currentDate.schoolYearStart
val baseDate = currentDate.firstSchoolDayInSchoolYear
val rangeStart = baseDate.toTimestamp()
val rangeEnd = LocalDate.now().plusWeeks(1).toTimestamp()

View File

@ -24,9 +24,9 @@ import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragme
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.schoolYearEnd
import io.github.wulkanowy.utils.schoolYearStart
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import java.time.LocalDate
@ -194,9 +194,9 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
}
override fun showDatePickerDialog(currentDate: LocalDate) {
val baseDate = currentDate.schoolYearStart
val baseDate = currentDate.firstSchoolDayInSchoolYear
val rangeStart = baseDate.toTimestamp()
val rangeEnd = LocalDate.now().schoolYearEnd.toTimestamp()
val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp()
val constraintsBuilder = CalendarConstraints.Builder().apply {
setValidator(SchoolDaysValidator(rangeStart, rangeEnd))

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.databinding.ItemTimetableAdditionalBinding
@ -14,6 +15,8 @@ class AdditionalLessonsAdapter @Inject constructor() :
var items = emptyList<TimetableAdditional>()
var onDeleteClickListener: (timetableAdditional: TimetableAdditional) -> Unit = {}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
@ -25,8 +28,12 @@ class AdditionalLessonsAdapter @Inject constructor() :
val item = items[position]
with(holder.binding) {
additionalLessonItemTime.text = "${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}"
additionalLessonItemTime.text =
"${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}"
additionalLessonItemSubject.text = item.subject
additionalLessonItemDelete.isVisible = item.isAddedByUser
additionalLessonItemDelete.setOnClickListener { onDeleteClickListener(item) }
}
}

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.MaterialDatePicker
@ -10,13 +11,15 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.databinding.FragmentTimetableAdditionalBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.schoolYearEnd
import io.github.wulkanowy.utils.schoolYearStart
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import java.time.LocalDate
@ -53,7 +56,9 @@ class AdditionalLessonsFragment :
override fun initView() {
with(binding.additionalLessonsRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = additionalLessonsAdapter
adapter = additionalLessonsAdapter.apply {
onDeleteClickListener = { presenter.onDeleteLessonsSelected(it) }
}
addItemDecoration(DividerItemDecoration(context))
}
@ -61,9 +66,7 @@ class AdditionalLessonsFragment :
additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
additionalLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
additionalLessonsSwipe.setProgressBackgroundColorSchemeColor(
requireContext().getThemeAttrColor(
R.attr.colorSwipeRefresh
)
requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
)
additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() }
additionalLessonsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
@ -72,6 +75,8 @@ class AdditionalLessonsFragment :
additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() }
additionalLessonsNextButton.setOnClickListener { presenter.onNextDay() }
openAddAdditionalLessonButton.setOnClickListener { presenter.onAdditionalLessonAddButtonClicked() }
additionalLessonsNavContainer.elevation = requireContext().dpToPx(8f)
}
}
@ -90,6 +95,10 @@ class AdditionalLessonsFragment :
}
}
override fun showSuccessMessage() {
getString(R.string.additional_lessons_delete_success)
}
override fun updateNavigationDay(date: String) {
binding.additionalLessonsNavDate.text = date
}
@ -131,21 +140,24 @@ class AdditionalLessonsFragment :
binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
override fun showAddAdditionalLessonDialog() {
(activity as? MainActivity)?.showDialogFragment(AdditionalLessonAddDialog.newInstance())
}
override fun showDatePickerDialog(currentDate: LocalDate) {
val now = LocalDate.now()
val startOfSchoolYear = now.schoolYearStart.toTimestamp()
val endOfSchoolYear = now.schoolYearEnd.toTimestamp()
val startOfSchoolYear = now.firstSchoolDayInSchoolYear.toTimestamp()
val endOfSchoolYear = now.lastSchoolDayInSchoolYear.toTimestamp()
val constraintsBuilder = CalendarConstraints.Builder().apply {
setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear))
setStart(startOfSchoolYear)
setEnd(endOfSchoolYear)
}
val datePicker =
MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
datePicker.addOnPositiveButtonClickListener {
val date = it.toLocalDateTime()
@ -157,6 +169,18 @@ class AdditionalLessonsFragment :
}
}
override fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.additional_lessons_delete_title))
.setItems(
arrayOf(
getString(R.string.additional_lessons_delete_one),
getString(R.string.additional_lessons_delete_series)
)
) { _, position -> presenter.onDeleteDialogSelectItem(position, timetableAdditional) }
.show()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.additional
import android.annotation.SuppressLint
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
@ -20,6 +21,7 @@ import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.LocalDate
import javax.inject.Inject
@ -63,6 +65,10 @@ class AdditionalLessonsPresenter @Inject constructor(
view?.showDatePickerDialog(currentDate)
}
fun onAdditionalLessonAddButtonClicked() {
view?.showAddAdditionalLessonDialog()
}
fun onDateSet(year: Int, month: Int, day: Int) {
loadData(LocalDate.of(year, month, day))
reloadView()
@ -98,6 +104,36 @@ class AdditionalLessonsPresenter @Inject constructor(
}.launch("holidays")
}
fun onDeleteLessonsSelected(timetableAdditional: TimetableAdditional) {
if (timetableAdditional.repeatId == null) {
deleteAdditionalLessons(timetableAdditional, false)
} else {
view?.showDeleteLessonDialog(timetableAdditional)
}
}
fun onDeleteDialogSelectItem(position: Int, timetableAdditional: TimetableAdditional) {
deleteAdditionalLessons(timetableAdditional, position == 1)
}
private fun deleteAdditionalLessons(
timetableAdditional: TimetableAdditional,
deleteSeries: Boolean
) {
presenterScope.launch {
Timber.i("Additional Lesson delete start")
runCatching { timetableRepository.deleteAdditional(timetableAdditional, deleteSeries) }
.onSuccess {
Timber.i("Additional Lesson delete: Success")
view?.showSuccessMessage()
}
.onFailure {
Timber.i("Additional Lesson delete result: An exception occurred")
errorHandler.dispatch(it)
}
}
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
currentDate = date
@ -111,7 +147,7 @@ class AdditionalLessonsPresenter @Inject constructor(
Status.SUCCESS -> {
Timber.i("Loading additional lessons lessons result: Success")
view?.apply {
updateData(it.data!!.additional.sortedBy { item -> item.date })
updateData(it.data!!.additional.sortedBy { item -> item.start })
showEmpty(it.data.additional.isEmpty())
showErrorView(false)
showContent(it.data.additional.isNotEmpty())

View File

@ -35,4 +35,10 @@ interface AdditionalLessonsView : BaseView {
fun showNextButton(show: Boolean)
fun showDatePickerDialog(currentDate: LocalDate)
fun showAddAdditionalLessonDialog()
fun showSuccessMessage()
fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional)
}

View File

@ -0,0 +1,188 @@
package io.github.wulkanowy.ui.modules.timetable.additional.add
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogAdditionalAddBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import java.time.LocalDate
import java.time.LocalTime
import javax.inject.Inject
@AndroidEntryPoint
class AdditionalLessonAddDialog : BaseDialogFragment<DialogAdditionalAddBinding>(),
AdditionalLessonAddView {
@Inject
lateinit var presenter: AdditionalLessonAddPresenter
companion object {
fun newInstance() = AdditionalLessonAddDialog()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogAdditionalAddBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
with(binding) {
additionalLessonDialogStartEdit.doOnTextChanged { _, _, _, _ ->
additionalLessonDialogStart.isErrorEnabled = false
additionalLessonDialogStart.error = null
}
additionalLessonDialogEndEdit.doOnTextChanged { _, _, _, _ ->
additionalLessonDialogEnd.isErrorEnabled = false
additionalLessonDialogEnd.error = null
}
additionalLessonDialogDateEdit.doOnTextChanged { _, _, _, _ ->
additionalLessonDialogDate.isErrorEnabled = false
additionalLessonDialogDate.error = null
}
additionalLessonDialogContentEdit.doOnTextChanged { _, _, _, _ ->
additionalLessonDialogContent.isErrorEnabled = false
additionalLessonDialogContent.error = null
}
additionalLessonDialogAdd.setOnClickListener {
presenter.onAddAdditionalClicked(
start = additionalLessonDialogStartEdit.text?.toString(),
end = additionalLessonDialogEndEdit.text?.toString(),
date = additionalLessonDialogDateEdit.text?.toString(),
content = additionalLessonDialogContentEdit.text?.toString(),
isRepeat = additionalLessonDialogRepeat.isChecked
)
}
additionalLessonDialogClose.setOnClickListener { dismiss() }
additionalLessonDialogDateEdit.setOnClickListener { presenter.showDatePicker() }
additionalLessonDialogStartEdit.setOnClickListener { presenter.showStartTimePicker() }
additionalLessonDialogEndEdit.setOnClickListener { presenter.showEndTimePicker() }
}
}
override fun showSuccessMessage() {
showMessage(getString(R.string.additional_lessons_add_success))
}
override fun setErrorDateRequired() {
with(binding.additionalLessonDialogDate) {
isErrorEnabled = true
error = getString(R.string.error_field_required)
}
}
override fun setErrorStartRequired() {
with(binding.additionalLessonDialogStart) {
isErrorEnabled = true
error = getString(R.string.error_field_required)
}
}
override fun setErrorEndRequired() {
with(binding.additionalLessonDialogEnd) {
isErrorEnabled = true
error = getString(R.string.error_field_required)
}
}
override fun setErrorContentRequired() {
with(binding.additionalLessonDialogContent) {
isErrorEnabled = true
error = getString(R.string.error_field_required)
}
}
override fun setErrorIncorrectEndTime() {
with(binding.additionalLessonDialogEnd) {
isErrorEnabled = true
error = getString(R.string.additional_lessons_end_time_error)
}
}
override fun closeDialog() {
dismiss()
}
override fun showDatePickerDialog(selectedDate: LocalDate) {
val rangeStart = LocalDate.now().toTimestamp()
val rangeEnd = LocalDate.now().lastSchoolDayInSchoolYear.toTimestamp()
val constraintsBuilder = CalendarConstraints.Builder().apply {
setStart(rangeStart)
setEnd(rangeEnd)
setValidator(SchoolDaysValidator(rangeStart, rangeEnd))
}
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(selectedDate.toTimestamp())
.build()
datePicker.addOnPositiveButtonClickListener {
val date = it.toLocalDateTime().toLocalDate()
presenter.onDateSelected(date)
binding.additionalLessonDialogDateEdit.setText(date.toFormattedString())
}
if (!parentFragmentManager.isStateSaved) {
datePicker.show(parentFragmentManager, null)
}
}
override fun showStartTimePickerDialog(selectedTime: LocalTime) {
showTimePickerDialog(selectedTime) {
presenter.onStartTimeSelected(it)
binding.additionalLessonDialogStartEdit.setText(it.toString())
}
}
override fun showEndTimePickerDialog(selectedTime: LocalTime) {
showTimePickerDialog(selectedTime) {
presenter.onEndTimeSelected(it)
binding.additionalLessonDialogEndEdit.setText(it.toString())
}
}
private fun showTimePickerDialog(defaultTime: LocalTime, onTimeSelected: (LocalTime) -> Unit) {
val timePicker = MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H)
.setHour(defaultTime.hour)
.setMinute(defaultTime.minute)
.build()
timePicker.addOnPositiveButtonClickListener {
onTimeSelected(LocalTime.of(timePicker.hour, timePicker.minute))
}
if (!parentFragmentManager.isStateSaved) {
timePicker.show(parentFragmentManager, null)
}
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,166 @@
package io.github.wulkanowy.ui.modules.timetable.additional.add
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toLocalDate
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import java.util.UUID
import javax.inject.Inject
class AdditionalLessonAddPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val timetableRepository: TimetableRepository,
private val semesterRepository: SemesterRepository
) : BasePresenter<AdditionalLessonAddView>(errorHandler, studentRepository) {
private var selectedStartTime = LocalTime.of(15, 0)
private var selectedEndTime = LocalTime.of(15, 45)
private var selectedDate = LocalDate.now()
override fun onAttachView(view: AdditionalLessonAddView) {
super.onAttachView(view)
view.initView()
Timber.i("AdditionalLesson details view was initialized")
}
fun showDatePicker() {
view?.showDatePickerDialog(selectedDate)
}
fun showStartTimePicker() {
view?.showStartTimePickerDialog(selectedStartTime)
}
fun showEndTimePicker() {
view?.showEndTimePickerDialog(selectedEndTime)
}
fun onStartTimeSelected(time: LocalTime) {
selectedStartTime = time
}
fun onEndTimeSelected(time: LocalTime) {
selectedEndTime = time
}
fun onDateSelected(date: LocalDate) {
selectedDate = date
}
fun onAddAdditionalClicked(
start: String?,
end: String?,
date: String?,
content: String?,
isRepeat: Boolean
) {
if (isUserInputValid(start, end, date, content)) {
addAdditionalLesson(
start = LocalTime.parse(start!!),
end = LocalTime.parse(end),
date = date!!.toLocalDate(),
subject = content!!,
isRepeat = isRepeat
)
}
}
private fun isUserInputValid(
start: String?,
end: String?,
date: String?,
content: String?
): Boolean {
var isValid = true
if (start.isNullOrBlank()) {
view?.setErrorStartRequired()
isValid = false
}
if (end.isNullOrBlank()) {
view?.setErrorEndRequired()
isValid = false
}
if (date.isNullOrBlank()) {
view?.setErrorDateRequired()
isValid = false
}
if (content.isNullOrBlank()) {
view?.setErrorContentRequired()
isValid = false
}
if (selectedStartTime >= selectedEndTime) {
view?.setErrorIncorrectEndTime()
isValid = false
}
return isValid
}
private fun addAdditionalLesson(
start: LocalTime,
end: LocalTime,
date: LocalDate,
subject: String,
isRepeat: Boolean
) {
presenterScope.launch {
val semester = runCatching {
val student = studentRepository.getCurrentStudent()
semesterRepository.getCurrentSemester(student)
}
.onFailure(errorHandler::dispatch)
.getOrNull() ?: return@launch
val weeks = if (isRepeat) {
ChronoUnit.WEEKS.between(date, date.lastSchoolDayInSchoolYear)
} else 0
val uniqueRepeatId = UUID.randomUUID().takeIf { isRepeat }
val lessonsToAdd = (0..weeks).map {
TimetableAdditional(
studentId = semester.studentId,
diaryId = semester.diaryId,
start = LocalDateTime.of(date, start),
end = LocalDateTime.of(date, end),
date = date.plusWeeks(it),
subject = subject
).apply {
isAddedByUser = true
repeatId = uniqueRepeatId
}
}
Timber.i("AdditionalLesson insert start")
runCatching { timetableRepository.saveAdditionalList(lessonsToAdd) }
.onSuccess {
Timber.i("AdditionalLesson insert: Success")
view?.run {
showSuccessMessage()
closeDialog()
}
}
.onFailure {
Timber.i("AdditionalLesson insert result: An exception occurred")
errorHandler.dispatch(it)
}
}
}
}

View File

@ -0,0 +1,30 @@
package io.github.wulkanowy.ui.modules.timetable.additional.add
import io.github.wulkanowy.ui.base.BaseView
import java.time.LocalDate
import java.time.LocalTime
interface AdditionalLessonAddView : BaseView {
fun initView()
fun closeDialog()
fun showDatePickerDialog(selectedDate: LocalDate)
fun showStartTimePickerDialog(selectedTime: LocalTime)
fun showEndTimePickerDialog(selectedTime: LocalTime)
fun showSuccessMessage()
fun setErrorDateRequired()
fun setErrorStartRequired()
fun setErrorEndRequired()
fun setErrorContentRequired()
fun setErrorIncorrectEndTime()
}

View File

@ -18,10 +18,10 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.SchoolDaysValidator
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear
import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.schoolYearEnd
import io.github.wulkanowy.utils.schoolYearStart
import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import java.time.LocalDate
@ -68,9 +68,7 @@ class CompletedLessonsFragment :
completedLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
completedLessonsSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
completedLessonsSwipe.setProgressBackgroundColorSchemeColor(
requireContext().getThemeAttrColor(
R.attr.colorSwipeRefresh
)
requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
)
completedLessonErrorRetry.setOnClickListener { presenter.onRetry() }
completedLessonErrorDetails.setOnClickListener { presenter.onDetailsClick() }
@ -154,19 +152,18 @@ class CompletedLessonsFragment :
override fun showDatePickerDialog(currentDate: LocalDate) {
val now = LocalDate.now()
val startOfSchoolYear = now.schoolYearStart.toTimestamp()
val endOfSchoolYear = now.schoolYearEnd.toTimestamp()
val startOfSchoolYear = now.firstSchoolDayInSchoolYear.toTimestamp()
val endOfSchoolYear = now.lastSchoolDayInSchoolYear.toTimestamp()
val constraintsBuilder = CalendarConstraints.Builder().apply {
setValidator(SchoolDaysValidator(startOfSchoolYear, endOfSchoolYear))
setStart(startOfSchoolYear)
setEnd(endOfSchoolYear)
}
val datePicker =
MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
datePicker.addOnPositiveButtonClickListener {
val date = it.toLocalDateTime()

View File

@ -85,35 +85,31 @@ inline val LocalDate.previousOrSameSchoolDay: LocalDate
inline val LocalDate.weekDayName: String
get() = format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()))
inline val LocalDate.monday: LocalDate
get() = with(MONDAY)
inline val LocalDate.monday: LocalDate get() = with(MONDAY)
inline val LocalDate.sunday: LocalDate
get() = with(SUNDAY)
inline val LocalDate.sunday: LocalDate get() = with(SUNDAY)
/**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/
inline val LocalDate.isHolidays: Boolean
get() = isBefore(firstSchoolDay) && isAfter(lastSchoolDay)
val LocalDate.isHolidays: Boolean
get() = isBefore(firstSchoolDayInCalendarYear) && isAfter(lastSchoolDayInCalendarYear)
inline val LocalDate.firstSchoolDay: LocalDate
get() = LocalDate.of(year, 9, 1).run {
when (dayOfWeek) {
FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY))
else -> this
}
val LocalDate.firstSchoolDayInSchoolYear: LocalDate
get() = withYear(if (this.monthValue <= 6) this.year - 1 else this.year).firstSchoolDayInCalendarYear
val LocalDate.lastSchoolDayInSchoolYear: LocalDate
get() = withYear(if (this.monthValue > 6) this.year + 1 else this.year).lastSchoolDayInCalendarYear
fun LocalDate.getLastSchoolDayIfHoliday(schoolYear: Int): LocalDate {
val date = LocalDate.of(schoolYear.getSchoolYearByMonth(monthValue), monthValue, dayOfMonth)
if (date.isHolidays) {
return date.lastSchoolDayInCalendarYear
}
inline val LocalDate.lastSchoolDay: LocalDate
get() = LocalDate.of(year, 6, 20)
.with(next(FRIDAY))
inline val LocalDate.schoolYearStart: LocalDate
get() = withYear(if (this.monthValue <= 6) this.year - 1 else this.year).firstSchoolDay
inline val LocalDate.schoolYearEnd: LocalDate
get() = withYear(if (this.monthValue > 6) this.year + 1 else this.year).lastSchoolDay
return date
}
private fun Int.getSchoolYearByMonth(monthValue: Int): Int {
return when (monthValue) {
@ -122,12 +118,15 @@ private fun Int.getSchoolYearByMonth(monthValue: Int): Int {
}
}
fun LocalDate.getLastSchoolDayIfHoliday(schoolYear: Int): LocalDate {
val date = LocalDate.of(schoolYear.getSchoolYearByMonth(monthValue), monthValue, dayOfMonth)
if (date.isHolidays) {
return date.lastSchoolDay
private inline val LocalDate.firstSchoolDayInCalendarYear: LocalDate
get() = LocalDate.of(year, 9, 1).run {
when (dayOfWeek) {
FRIDAY, SATURDAY, SUNDAY -> with(firstInMonth(MONDAY))
else -> this
}
}
return date
}
private inline val LocalDate.lastSchoolDayInCalendarYear: LocalDate
get() = LocalDate.of(year, 6, 20)
.with(next(FRIDAY))