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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 3222 additions and 91 deletions

View File

@ -167,7 +167,7 @@ huaweiPublish {
ext {
work_manager = "2.7.1"
android_hilt = "1.0.0"
room = "2.3.0"
room = "2.4.0"
chucker = "3.5.2"
mockk = "1.12.1"
coroutines = "1.5.2"

File diff suppressed because it is too large Load Diff

View File

@ -114,7 +114,6 @@
</intent-filter>
</service>
<receiver
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
android:exported="true"

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,11 +100,14 @@ 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()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()

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,18 +140,21 @@ 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()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()
@ -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,16 +152,15 @@ 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()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraintsBuilder.build())
.setSelection(currentDate.toTimestamp())
.build()

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

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
<path
android:fillColor="@android:color/white"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,9h14v10zM19,7L5,7L5,5h14v2zM7,11h5v5L7,16z" />
</vector>

View File

@ -0,0 +1,156 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:minWidth="300dp"
android:paddingStart="8dp"
android:paddingEnd="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical">
<TextView
android:id="@+id/addadditionalLessonHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/additional_lessons_add_title"
android:textSize="21sp"
android:textStyle="bold" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/additionalLessonDialogDate"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="28dp"
android:hint="@string/all_date"
app:startIconDrawable="@drawable/ic_calendat_all">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/additionalLessonDialogDateEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
android:inputType="text"
tools:ignore="Deprecated" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/additionalLessonDialogRepeat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp"
android:text="@string/additional_lessons_repeat" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/additionalLessonDialogStart"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp"
android:hint="@string/additional_lessons_start"
app:startIconDrawable="@drawable/ic_all_clock">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/additionalLessonDialogStartEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
android:inputType="text"
tools:ignore="Deprecated" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/additionalLessonDialogEnd"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:hint="@string/additional_lessons_end"
app:startIconDrawable="@drawable/ic_all_clock">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/additionalLessonDialogEndEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:focusable="false"
android:inputType="text"
tools:ignore="Deprecated" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/additionalLessonDialogContent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:hint="@string/all_subject">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/additionalLessonDialogContentEdit"
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end"
android:minHeight="52dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/additionalLessonDialogClose"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginBottom="8dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:minWidth="88dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/additionalLessonDialogAdd"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_gravity="center_vertical"
android:layout_marginBottom="8dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:minWidth="88dp"
android:text="@string/all_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</ScrollView>

View File

@ -204,7 +204,6 @@
android:insetBottom="0dp"
android:minWidth="88dp"
android:text="@string/all_close" />
</RelativeLayout>
</LinearLayout>

View File

@ -5,8 +5,8 @@
android:layout_height="match_parent"
android:fillViewport="true"
android:minWidth="300dp"
android:paddingStart="24dp"
android:paddingEnd="24dp">
android:paddingStart="8dp"
android:paddingEnd="8dp">
<LinearLayout
android:layout_width="match_parent"
@ -18,7 +18,7 @@
android:id="@+id/addHomeworkHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/homework_add"
@ -30,10 +30,10 @@
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="28dp"
android:hint="@string/all_date"
app:startIconDrawable="@drawable/ic_main_timetable">
app:startIconDrawable="@drawable/ic_calendat_all">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/homeworkDialogDateEdit"
@ -51,7 +51,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:hint="@string/all_subject">
@ -66,7 +66,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:hint="@string/all_teacher">
@ -81,7 +81,7 @@
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:hint="@string/all_content">
@ -105,13 +105,12 @@
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:minWidth="88dp"
android:text="@string/all_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
@ -126,6 +125,7 @@
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:minWidth="88dp"
android:text="@string/all_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

View File

@ -26,6 +26,8 @@
android:id="@+id/additionalLessonsRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="64dp"
tools:listitem="@layout/item_timetable_additional" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -108,6 +110,18 @@
android:text="@string/all_retry" />
</LinearLayout>
</LinearLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/openAddAdditionalLessonButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:clickable="true"
android:focusable="true"
android:text="@string/additional_lessons_add"
android:tint="?colorOnSecondary"
app:icon="@drawable/ic_all_add" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout

View File

@ -17,13 +17,13 @@
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/additionalLessonItemDelete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
tools:maxLines="2"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/additionalLessonItemTime"
@ -38,4 +38,16 @@
app:layout_constraintTop_toBottomOf="@id/additionalLessonItemSubject"
tools:text="11:11 - 12:12" />
<ImageButton
android:id="@+id/additionalLessonItemDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:padding="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_menu_message_delete"
app:tint="?colorPrimary"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -198,6 +198,17 @@
<string name="additional_lessons_title">Additional lessons</string>
<string name="additional_lessons_button">Show additional lessons</string>
<string name="additional_lessons_no_items">No info about additional lessons</string>
<string name="additional_lessons_add">New lesson</string>
<string name="additional_lessons_add_title">New additional lesson</string>
<string name="additional_lessons_add_success">Additional lesson added successfully</string>
<string name="additional_lessons_delete_success">Additional lesson deleted successfully</string>
<string name="additional_lessons_repeat">Repeat weekly</string>
<string name="additional_lessons_delete_title">Delete additional lesson</string>
<string name="additional_lessons_delete_one">Just this lesson</string>
<string name="additional_lessons_delete_series">All in the series</string>
<string name="additional_lessons_start">Start time</string>
<string name="additional_lessons_end">End time</string>
<string name="additional_lessons_end_time_error">End time must be greater than start time</string>
<!--Attendance-->

View File

@ -1,6 +1,6 @@
buildscript {
ext {
kotlin_version = '1.6.0'
kotlin_version = '1.6.10'
about_libraries = '8.9.4'
hilt_version = "2.40.5"
}