mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-18 13:26:44 -06:00
Add excusing for absence (#533)
This commit is contained in:
parent
0c5d45717c
commit
19f495cba6
1652
app/schemas/io.github.wulkanowy.data.db.AppDatabase/21.json
Normal file
1652
app/schemas/io.github.wulkanowy.data.db.AppDatabase/21.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -35,9 +35,9 @@ class AttendanceLocalTest {
|
||||
@Test
|
||||
fun saveAndReadTest() {
|
||||
attendanceLocal.saveAttendance(listOf(
|
||||
Attendance(1, 2, LocalDate.of(2018, 9, 10), 0, "", "", false, false, false, false, false, false),
|
||||
Attendance(1, 2, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false),
|
||||
Attendance(1, 2, LocalDate.of(2018, 9, 17), 0, "", "", false, false, false, false, false, false)
|
||||
Attendance(1, 2, 3, LocalDate.of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name),
|
||||
Attendance(1, 2, 3, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
|
||||
Attendance(1, 2, 3, LocalDate.of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
|
||||
))
|
||||
|
||||
val attendance = attendanceLocal
|
||||
|
@ -2,8 +2,8 @@ package io.github.wulkanowy.data.repositories.timetable
|
||||
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import org.threeten.bp.LocalDateTime.now
|
||||
import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote
|
||||
import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal
|
||||
import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote
|
||||
|
||||
fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal {
|
||||
return TimetableLocal(
|
||||
|
@ -61,6 +61,7 @@ import io.github.wulkanowy.data.db.migrations.Migration18
|
||||
import io.github.wulkanowy.data.db.migrations.Migration19
|
||||
import io.github.wulkanowy.data.db.migrations.Migration2
|
||||
import io.github.wulkanowy.data.db.migrations.Migration20
|
||||
import io.github.wulkanowy.data.db.migrations.Migration21
|
||||
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
@ -102,7 +103,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 20
|
||||
const val VERSION_SCHEMA = 21
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
@ -124,7 +125,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration17(),
|
||||
Migration18(),
|
||||
Migration19(sharedPrefProvider),
|
||||
Migration20()
|
||||
Migration20(),
|
||||
Migration21()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@ data class Attendance(
|
||||
@ColumnInfo(name = "diary_id")
|
||||
val diaryId: Int,
|
||||
|
||||
@ColumnInfo(name = "time_id")
|
||||
val timeId: Int,
|
||||
|
||||
val date: LocalDate,
|
||||
|
||||
val number: Int,
|
||||
@ -33,7 +36,13 @@ data class Attendance(
|
||||
|
||||
val excused: Boolean,
|
||||
|
||||
val deleted: Boolean
|
||||
val deleted: Boolean,
|
||||
|
||||
val excusable: Boolean,
|
||||
|
||||
@ColumnInfo(name = "excuse_status")
|
||||
val excuseStatus: String?
|
||||
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
|
@ -0,0 +1,13 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration21 : Migration(20, 21) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0")
|
||||
database.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0")
|
||||
database.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL")
|
||||
}
|
||||
}
|
@ -3,8 +3,11 @@ package io.github.wulkanowy.data.repositories.attendance
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Absent
|
||||
import io.reactivex.Single
|
||||
import org.threeten.bp.LocalDate
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import org.threeten.bp.LocalTime
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -19,6 +22,7 @@ class AttendanceRemote @Inject constructor(private val sdk: Sdk) {
|
||||
studentId = semester.studentId,
|
||||
diaryId = semester.diaryId,
|
||||
date = it.date,
|
||||
timeId = it.timeId,
|
||||
number = it.number,
|
||||
subject = it.subject,
|
||||
name = it.name,
|
||||
@ -27,9 +31,20 @@ class AttendanceRemote @Inject constructor(private val sdk: Sdk) {
|
||||
exemption = it.exemption,
|
||||
lateness = it.lateness,
|
||||
excused = it.excused,
|
||||
deleted = it.deleted
|
||||
deleted = it.deleted,
|
||||
excusable = it.excusable,
|
||||
excuseStatus = it.excuseStatus?.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun excuseAbsence(semester: Semester, absenceList: List<Attendance>, reason: String?): Single<Boolean> {
|
||||
return sdk.switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
||||
Absent(
|
||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||
timeId = attendance.timeId
|
||||
)
|
||||
}, reason)
|
||||
}
|
||||
}
|
||||
|
@ -41,4 +41,8 @@ class AttendanceRepository @Inject constructor(
|
||||
}).map { list -> list.filter { it.date in startDate..endDate } }
|
||||
}
|
||||
}
|
||||
|
||||
fun excuseForAbsence(semester: Semester, attendanceList: List<Attendance>, reason: String? = null): Single<Boolean> {
|
||||
return remote.excuseAbsence(semester, attendanceList, reason)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package io.github.wulkanowy.data.repositories.attendance
|
||||
|
||||
enum class SentExcuseStatus(val id: Int = 0) {
|
||||
WAITING,
|
||||
ACCEPTED,
|
||||
DENIED
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
|
||||
class AttendanceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) {
|
||||
|
||||
var excuseActionMode: Boolean = false
|
||||
|
||||
var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> }
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import android.content.DialogInterface.BUTTON_POSITIVE
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@ -10,8 +11,9 @@ import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
@ -24,6 +26,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
||||
import kotlinx.android.synthetic.main.dialog_excuse.*
|
||||
import kotlinx.android.synthetic.main.fragment_attendance.*
|
||||
import org.threeten.bp.LocalDate
|
||||
import javax.inject.Inject
|
||||
@ -35,7 +38,13 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
lateinit var presenter: AttendancePresenter
|
||||
|
||||
@Inject
|
||||
lateinit var attendanceAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
||||
lateinit var attendanceAdapter: AttendanceAdapter<AbstractFlexibleItem<*>>
|
||||
|
||||
override val excuseSuccessString: String
|
||||
get() = getString(R.string.attendance_excuse_success)
|
||||
|
||||
override val excuseNoSelectionString: String
|
||||
get() = getString(R.string.attendance_excuse_no_selection)
|
||||
|
||||
companion object {
|
||||
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
||||
@ -49,6 +58,34 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
|
||||
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
|
||||
|
||||
override val excuseActionMode: Boolean get() = attendanceAdapter.excuseActionMode
|
||||
|
||||
private var actionMode: ActionMode? = null
|
||||
private val actionModeCallback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val inflater = mode.menuInflater
|
||||
inflater.inflate(R.menu.context_menu_excuse, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = getString(R.string.attendance_excuse_title)
|
||||
return presenter.onPrepareActionMode()
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
presenter.onDestroyActionMode()
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
||||
return when (menu.itemId) {
|
||||
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
@ -66,6 +103,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
|
||||
override fun initView() {
|
||||
attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected)
|
||||
attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect
|
||||
|
||||
with(attendanceRecycler) {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
||||
@ -83,6 +121,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
attendanceNavDate.setOnClickListener { presenter.onPickDate() }
|
||||
attendanceNextButton.setOnClickListener { presenter.onNextDay() }
|
||||
|
||||
attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
|
||||
|
||||
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||
}
|
||||
|
||||
@ -115,6 +155,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
if (::presenter.isInitialized) presenter.onViewReselected()
|
||||
}
|
||||
|
||||
override fun onFragmentChanged() {
|
||||
if (::presenter.isInitialized) presenter.onMainViewChanged()
|
||||
}
|
||||
|
||||
override fun popView() {
|
||||
(activity as? MainActivity)?.popView()
|
||||
}
|
||||
@ -155,6 +199,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
override fun showExcuseButton(show: Boolean) {
|
||||
attendanceExcuseButton.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showAttendanceDialog(lesson: Attendance) {
|
||||
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
|
||||
}
|
||||
@ -174,10 +222,38 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExcuseDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.attendance_excuse_title)
|
||||
.setView(R.layout.dialog_excuse)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
.create()
|
||||
.apply {
|
||||
setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ ->
|
||||
presenter.onExcuseDialogSubmit(excuseReason.text?.toString().orEmpty())
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
override fun openSummaryView() {
|
||||
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun startActionMode() {
|
||||
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
|
||||
}
|
||||
|
||||
override fun showExcuseCheckboxes(show: Boolean) {
|
||||
attendanceAdapter.apply {
|
||||
excuseActionMode = show
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun finishActionMode() {
|
||||
actionMode?.finish()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
|
||||
|
@ -1,18 +1,22 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.view.isVisible
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_attendance.*
|
||||
|
||||
class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
|
||||
class AttendanceItem(val attendance: Attendance) :
|
||||
AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.item_attendance
|
||||
|
||||
@ -26,6 +30,34 @@ class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<Attendan
|
||||
attendanceItemSubject.text = attendance.subject
|
||||
attendanceItemDescription.text = attendance.name
|
||||
attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE }
|
||||
attendanceItemNumber.visibility = GONE
|
||||
attendanceItemExcuseInfo.visibility = GONE
|
||||
attendanceItemExcuseCheckbox.visibility = GONE
|
||||
attendanceItemExcuseCheckbox.isChecked = false
|
||||
attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked ->
|
||||
(adapter as AttendanceAdapter).onExcuseCheckboxSelect(attendance, checked)
|
||||
}
|
||||
|
||||
when (if (attendance.excuseStatus != null) SentExcuseStatus.valueOf(attendance.excuseStatus) else null) {
|
||||
SentExcuseStatus.WAITING -> {
|
||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
||||
attendanceItemExcuseInfo.visibility = VISIBLE
|
||||
attendanceItemAlert.visibility = INVISIBLE
|
||||
}
|
||||
SentExcuseStatus.DENIED -> {
|
||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
|
||||
attendanceItemExcuseInfo.visibility = VISIBLE
|
||||
}
|
||||
else -> {
|
||||
if (attendance.excusable && (adapter as AttendanceAdapter).excuseActionMode) {
|
||||
attendanceItemNumber.visibility = GONE
|
||||
attendanceItemExcuseCheckbox.visibility = VISIBLE
|
||||
} else {
|
||||
attendanceItemNumber.visibility = VISIBLE
|
||||
attendanceItemExcuseCheckbox.visibility = GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,8 +78,20 @@ class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<Attendan
|
||||
return result
|
||||
}
|
||||
|
||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
||||
class ViewHolder(view: View, val adapter: FlexibleAdapter<*>) :
|
||||
FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View
|
||||
get() = contentView
|
||||
|
||||
override fun onClick(view: View?) {
|
||||
super.onClick(view)
|
||||
attendanceItemExcuseCheckbox.apply {
|
||||
if ((adapter as AttendanceAdapter).excuseActionMode && isVisible) {
|
||||
isChecked = !isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
|
||||
@Module
|
||||
class AttendanceModule {
|
||||
|
||||
@Provides
|
||||
fun provideAttendanceFlexibleAdapter() = AttendanceAdapter<AbstractFlexibleItem<*>>()
|
||||
}
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.attendance
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
|
||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
@ -40,6 +41,8 @@ class AttendancePresenter @Inject constructor(
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
private val attendanceToExcuseList = mutableListOf<Attendance>()
|
||||
|
||||
fun onAttachView(view: AttendanceView, date: Long?) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
@ -51,11 +54,15 @@ class AttendancePresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onPreviousDay() {
|
||||
view?.finishActionMode()
|
||||
attendanceToExcuseList.clear()
|
||||
loadData(currentDate.previousSchoolDay)
|
||||
reloadView()
|
||||
}
|
||||
|
||||
fun onNextDay() {
|
||||
view?.finishActionMode()
|
||||
attendanceToExcuseList.clear()
|
||||
loadData(currentDate.nextSchoolDay)
|
||||
reloadView()
|
||||
}
|
||||
@ -100,10 +107,59 @@ class AttendancePresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onMainViewChanged() {
|
||||
view?.finishActionMode()
|
||||
}
|
||||
|
||||
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
|
||||
if (item is AttendanceItem) {
|
||||
Timber.i("Select attendance item ${item.attendance.id}")
|
||||
view?.showAttendanceDialog(item.attendance)
|
||||
view?.apply {
|
||||
if (item is AttendanceItem && !excuseActionMode) {
|
||||
Timber.i("Select attendance item ${item.attendance.id}")
|
||||
showAttendanceDialog(item.attendance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onExcuseButtonClick() {
|
||||
view?.startActionMode()
|
||||
}
|
||||
|
||||
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
|
||||
if (checked) attendanceToExcuseList.add(attendanceItem)
|
||||
else attendanceToExcuseList.remove(attendanceItem)
|
||||
}
|
||||
|
||||
fun onExcuseSubmitButtonClick(): Boolean {
|
||||
view?.apply {
|
||||
return if (attendanceToExcuseList.isNotEmpty()) {
|
||||
showExcuseDialog()
|
||||
true
|
||||
} else {
|
||||
showMessage(excuseNoSelectionString)
|
||||
false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onExcuseDialogSubmit(reason: String) {
|
||||
view?.finishActionMode()
|
||||
excuseAbsence(if (reason != "") reason else null, attendanceToExcuseList.toList())
|
||||
}
|
||||
|
||||
fun onPrepareActionMode(): Boolean {
|
||||
view?.apply {
|
||||
showExcuseCheckboxes(true)
|
||||
showExcuseButton(false)
|
||||
}
|
||||
attendanceToExcuseList.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onDestroyActionMode() {
|
||||
view?.apply {
|
||||
showExcuseCheckboxes(false)
|
||||
showExcuseButton(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,6 +213,7 @@ class AttendancePresenter @Inject constructor(
|
||||
showEmpty(it.isEmpty())
|
||||
showErrorView(false)
|
||||
showContent(it.isNotEmpty())
|
||||
showExcuseButton(it.any { item -> item.attendance.excusable })
|
||||
}
|
||||
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
|
||||
}) {
|
||||
@ -167,6 +224,39 @@ class AttendancePresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun excuseAbsence(reason: String?, toExcuseList: List<Attendance>) {
|
||||
Timber.i("Excusing absence started")
|
||||
disposable.apply {
|
||||
add(studentRepository.getCurrentStudent()
|
||||
.delay(200, MILLISECONDS)
|
||||
.flatMap { semesterRepository.getCurrentSemester(it) }
|
||||
.flatMap { attendanceRepository.excuseForAbsence(it, toExcuseList, reason) }
|
||||
.subscribeOn(schedulers.backgroundThread)
|
||||
.observeOn(schedulers.mainThread)
|
||||
.doOnSubscribe {
|
||||
view?.apply {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
showExcuseButton(false)
|
||||
}
|
||||
}
|
||||
.subscribe({
|
||||
Timber.i("Excusing for absence result: Success")
|
||||
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
|
||||
attendanceToExcuseList.clear()
|
||||
view?.apply {
|
||||
showExcuseButton(false)
|
||||
showMessage(excuseSuccessString)
|
||||
}
|
||||
loadData(currentDate, true)
|
||||
}) {
|
||||
Timber.i("Excusing for absence result: An exception occurred")
|
||||
view?.showProgress(false)
|
||||
errorHandler.dispatch(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
if (isViewEmpty) {
|
||||
|
@ -10,6 +10,12 @@ interface AttendanceView : BaseView {
|
||||
|
||||
val currentStackSize: Int?
|
||||
|
||||
val excuseSuccessString: String
|
||||
|
||||
val excuseNoSelectionString: String
|
||||
|
||||
val excuseActionMode: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AttendanceItem>)
|
||||
@ -38,11 +44,21 @@ interface AttendanceView : BaseView {
|
||||
|
||||
fun showNextButton(show: Boolean)
|
||||
|
||||
fun showExcuseButton(show: Boolean)
|
||||
|
||||
fun showAttendanceDialog(lesson: Attendance)
|
||||
|
||||
fun showDatePickerDialog(currentDate: LocalDate)
|
||||
|
||||
fun showExcuseDialog()
|
||||
|
||||
fun openSummaryView()
|
||||
|
||||
fun startActionMode()
|
||||
|
||||
fun showExcuseCheckboxes(show: Boolean)
|
||||
|
||||
fun finishActionMode()
|
||||
|
||||
fun popView()
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.safelyPopFragments
|
||||
import io.github.wulkanowy.utils.setOnViewChangeListener
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainActivity : BaseActivity<MainPresenter>(), MainView {
|
||||
@ -167,6 +168,11 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
|
||||
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected()
|
||||
}
|
||||
|
||||
override fun notifyMenuViewChanged() {
|
||||
Timber.d("Menu view changed")
|
||||
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentChanged()
|
||||
}
|
||||
|
||||
fun showDialogFragment(dialog: DialogFragment) {
|
||||
navController.showDialogFragment(dialog)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
|
||||
import io.github.wulkanowy.ui.modules.about.license.LicenseModule
|
||||
import io.github.wulkanowy.ui.modules.account.AccountDialog
|
||||
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.AttendanceModule
|
||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||
@ -51,7 +52,7 @@ abstract class MainModule {
|
||||
}
|
||||
|
||||
@PerFragment
|
||||
@ContributesAndroidInjector
|
||||
@ContributesAndroidInjector(modules = [AttendanceModule::class])
|
||||
abstract fun bindAttendanceFragment(): AttendanceFragment
|
||||
|
||||
@PerFragment
|
||||
|
@ -75,6 +75,7 @@ class MainPresenter @Inject constructor(
|
||||
notifyMenuViewReselected()
|
||||
false
|
||||
} else {
|
||||
notifyMenuViewChanged()
|
||||
switchMenuView(index)
|
||||
true
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ interface MainView : BaseView {
|
||||
|
||||
fun notifyMenuViewReselected()
|
||||
|
||||
fun notifyMenuViewChanged()
|
||||
|
||||
fun setViewTitle(title: String)
|
||||
|
||||
fun popView(depth: Int = 1)
|
||||
@ -33,6 +35,8 @@ interface MainView : BaseView {
|
||||
interface MainChildView {
|
||||
|
||||
fun onFragmentReselected()
|
||||
|
||||
fun onFragmentChanged() {}
|
||||
}
|
||||
|
||||
interface TitledView {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/colorPrimaryLight"/>
|
||||
<corners android:radius="12dp"/>
|
||||
<solid android:color="@color/colorPrimaryLight" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/colorPrimary"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
||||
<solid android:color="@color/colorPrimary" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
|
10
app/src/main/res/drawable/ic_all_done.xml
Normal file
10
app/src/main/res/drawable/ic_all_done.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_all_done_all.xml
Normal file
10
app/src/main/res/drawable/ic_all_done_all.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_excuse_denied.xml
Normal file
10
app/src/main/res/drawable/ic_excuse_denied.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7,11v2h10v-2L7,11zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_excuse_waiting.xml
Normal file
10
app/src/main/res/drawable/ic_excuse_waiting.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
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,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
|
||||
</vector>
|
21
app/src/main/res/layout/dialog_excuse.xml
Normal file
21
app/src/main/res/layout/dialog_excuse.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/excuseReason"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/attendance_excuse_dialog_reason" />
|
||||
|
||||
<requestFocus />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
@ -40,16 +40,16 @@
|
||||
<TextView
|
||||
android:id="@+id/gradeDialogColorAndWeightValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="2dp"
|
||||
android:minHeight="32dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="@color/grade_black"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textIsSelectable="true"
|
||||
android:maxLines="2"
|
||||
android:minHeight="32dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="14sp"
|
||||
tools:text="Waga: 1.00" />
|
||||
</LinearLayout>
|
||||
|
||||
@ -57,16 +57,17 @@
|
||||
android:id="@+id/gradeDialogHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="120dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_toLeftOf="@+id/gradeDialogValueLayout"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_toStartOf="@+id/gradeDialogValueLayout"
|
||||
android:layout_toLeftOf="@+id/gradeDialogValueLayout"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="120dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeDialogSubject"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -56,6 +56,20 @@
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/attendanceExcuseButton"
|
||||
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:tint="?colorOnSecondary"
|
||||
android:visibility="gone"
|
||||
android:text="@string/attendance_excuse_title"
|
||||
app:icon="@drawable/ic_all_done_all"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/attendanceError"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -1,5 +1,4 @@
|
||||
<RelativeLayout 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:id="@+id/gradeHeaderContainer"
|
||||
android:layout_width="match_parent"
|
||||
@ -53,15 +52,15 @@
|
||||
<TextView
|
||||
android:id="@+id/gradeHeaderNote"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="5dp"
|
||||
android:background="@drawable/background_header_note"
|
||||
android:gravity="center"
|
||||
android:minWidth="20dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="5dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/background_header_note"
|
||||
android:paddingRight="5dp"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textSize="14sp"
|
||||
tools:text="255" />
|
||||
|
@ -13,16 +13,40 @@
|
||||
android:paddingBottom="7dp"
|
||||
tools:context=".ui.modules.attendance.AttendanceItem">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceItemNumber"
|
||||
<LinearLayout
|
||||
android:id="@+id/attendanceItemNumberContainer"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLength="2"
|
||||
android:textSize="32sp"
|
||||
tools:text="5" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceItemNumber"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLength="2"
|
||||
android:textSize="32sp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
tools:text="5" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/attendanceItemExcuseCheckbox"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:text="@null"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/attendanceItemExcuseInfo"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_excuse_waiting"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceItemSubject"
|
||||
@ -35,8 +59,8 @@
|
||||
android:layout_marginRight="40dp"
|
||||
android:layout_toStartOf="@id/attendanceItemAlert"
|
||||
android:layout_toLeftOf="@id/attendanceItemAlert"
|
||||
android:layout_toEndOf="@+id/attendanceItemNumber"
|
||||
android:layout_toRightOf="@+id/attendanceItemNumber"
|
||||
android:layout_toEndOf="@+id/attendanceItemNumberContainer"
|
||||
android:layout_toRightOf="@+id/attendanceItemNumberContainer"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="17sp"
|
||||
@ -48,7 +72,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignStart="@id/attendanceItemSubject"
|
||||
android:layout_alignLeft="@id/attendanceItemSubject"
|
||||
android:layout_alignBottom="@+id/attendanceItemNumber"
|
||||
android:layout_alignBottom="@+id/attendanceItemNumberContainer"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
|
11
app/src/main/res/menu/context_menu_excuse.xml
Normal file
11
app/src/main/res/menu/context_menu_excuse.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/excuseMenuSubmit"
|
||||
android:icon="@drawable/ic_all_done"
|
||||
android:title="@string/attendance_excuse_dialog_submit"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
@ -141,6 +141,11 @@
|
||||
<item quantity="many">%1$d nieobecności</item>
|
||||
<item quantity="other">%1$d nieobecności</item>
|
||||
</plurals>
|
||||
<string name="attendance_excuse_dialog_reason">Powód nieobecności (opcjonalny)</string>
|
||||
<string name="attendance_excuse_dialog_submit">Wyślij</string>
|
||||
<string name="attendance_excuse_success">Usprawiedliwiono pomyślnie!</string>
|
||||
<string name="attendance_excuse_no_selection">Musisz wybrać co najmniej jedną nieobecność!</string>
|
||||
<string name="attendance_excuse_title">Usprawiedliw</string>
|
||||
|
||||
|
||||
<!--Attendance summary-->
|
||||
|
@ -135,6 +135,11 @@
|
||||
<item quantity="one">%1$d absence</item>
|
||||
<item quantity="other">%1$d absences</item>
|
||||
</plurals>
|
||||
<string name="attendance_excuse_dialog_reason">Absence reason (optional)</string>
|
||||
<string name="attendance_excuse_dialog_submit">Send</string>
|
||||
<string name="attendance_excuse_success">Absence excused successfully!</string>
|
||||
<string name="attendance_excuse_no_selection">You must select at least one absence!</string>
|
||||
<string name="attendance_excuse_title">Excuse</string>
|
||||
|
||||
|
||||
<!--Attendance summary-->
|
||||
|
@ -16,6 +16,7 @@
|
||||
<style name="WulkanowyTheme.NoActionBar" parent="WulkanowyTheme">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="WulkanowyTheme.SplashScreen" parent="WulkanowyTheme.NoActionBar">
|
||||
|
Loading…
x
Reference in New Issue
Block a user