forked from github/wulkanowy-mirror
Add additional lessons feature (#1550)
This commit is contained in:

committed by
GitHub

parent
cc22985dc5
commit
094df212b4
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ data class Semester(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
|
||||
@ColumnInfo(name = "is_current")
|
||||
var current: Boolean = false
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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())
|
||||
|
@ -35,4 +35,10 @@ interface AdditionalLessonsView : BaseView {
|
||||
fun showNextButton(show: Boolean)
|
||||
|
||||
fun showDatePickerDialog(currentDate: LocalDate)
|
||||
|
||||
fun showAddAdditionalLessonDialog()
|
||||
|
||||
fun showSuccessMessage()
|
||||
|
||||
fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
|
@ -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))
|
||||
|
||||
|
Reference in New Issue
Block a user