Skip to content

Commit

Permalink
Send attendance in-app
Browse files Browse the repository at this point in the history
  • Loading branch information
kyori19 committed Oct 29, 2020
1 parent 2c858e2 commit dcea531
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 26 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/user.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.setFragmentResultListener
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import com.tingyik90.snackprogressbar.SnackProgressBar
import com.tingyik90.snackprogressbar.SnackProgressBarManager
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.fragment_course_detail.*
import net.accelf.itc_lms_unofficial.R
import net.accelf.itc_lms_unofficial.coursedetail.SendAttendanceDialogFragment.Companion.BUNDLE_RESULT
import net.accelf.itc_lms_unofficial.di.CustomLinkMovementMethod
import net.accelf.itc_lms_unofficial.file.download.Downloadable
import net.accelf.itc_lms_unofficial.models.*
Expand Down Expand Up @@ -49,6 +51,15 @@ class CourseDetailFragment : Fragment(R.layout.fragment_course_detail), NotifyLi
.setAllowUserInput(true)
}

private val attendanceSendSnackProgressBar by lazy {
SnackProgressBar(
SnackProgressBar.TYPE_HORIZONTAL,
getString(R.string.snackbar_fetching_attendance_send)
)
.setIsIndeterminate(true)
.setAllowUserInput(true)
}

private val customTabsIntent by lazy {
CustomTabsIntent.Builder()
.setShowTitle(true)
Expand Down Expand Up @@ -92,6 +103,19 @@ class CourseDetailFragment : Fragment(R.layout.fragment_course_detail), NotifyLi
}
}

private val sendAttendanceSuccessDialog by lazy {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.dialog_title_send_attendance_success)
.setMessage(R.string.dialog_message_send_attendance_success)
.setPositiveButton(R.string.button_dialog_close) { dialog, _ ->
dialog.dismiss()
}
.setOnDismissListener {
viewModel.closeSendAttendance()
viewModel.load()
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand All @@ -111,6 +135,17 @@ class CourseDetailFragment : Fragment(R.layout.fragment_course_detail), NotifyLi
}
}

showViewsAndDoWhen(courseDetail.sendAttendanceId != null, buttonSendAttendance) {
buttonSendAttendance.setOnClickListener {
if (!viewModel.prepareForSendingAttendance(courseDetail.sendAttendanceId!!)) {
Toast.makeText(requireContext(),
R.string.toast_already_fetching,
Toast.LENGTH_SHORT)
.show()
}
}
}

textTeachersName.text = courseDetail.teachers.joinToString(", ")
textCourseSummary.apply {
text = courseDetail.summary.fromHtml()
Expand Down Expand Up @@ -178,28 +213,38 @@ class CourseDetailFragment : Fragment(R.layout.fragment_course_detail), NotifyLi
)
}

viewModel.notifyDetail.observe(viewLifecycleOwner) {
if (it == null) {
return@observe
viewModel.notifyDetail.withSnackProgressBar(viewLifecycleOwner,
notifySnackProgressBar,
snackProgressBarManager,
{ viewModel.closeNotify() }) {
notifyDialog.apply {
setTitle(it.title)
setMessage(it.text.fromHtml().autoLink())
show()
}
}

when (it) {
is Loading -> {
snackProgressBarManager.show(
notifySnackProgressBar,
SnackProgressBarManager.LENGTH_INDEFINITE
)
}
is Success -> {
snackProgressBarManager.dismiss()
viewModel.attendanceSend.withSnackProgressBar(viewLifecycleOwner,
attendanceSendSnackProgressBar,
snackProgressBarManager,
{ viewModel.closeSendAttendance() }) {
if (it.success) {
sendAttendanceSuccessDialog.show()
return@withSnackProgressBar
}

notifyDialog.apply {
setTitle(it.data.title)
setMessage(it.data.text.fromHtml().autoLink())
show()
}
}
else -> {
SendAttendanceDialogFragment.newInstance()
.show(parentFragmentManager, SendAttendanceDialogFragment::class.java.simpleName)
}

setFragmentResultListener(SendAttendanceDialogFragment::class.java.simpleName) { _, it ->
@Suppress("UNCHECKED_CAST")
(it.getSerializable(BUNDLE_RESULT) as Result<SendAttendanceDialogResult>).onSuccess {
if (!viewModel.sendAttendance(it.attendancePassword, it.attendanceComment)) {
Toast.makeText(requireContext(),
R.string.toast_already_fetching,
Toast.LENGTH_SHORT)
.show()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import androidx.lifecycle.SavedStateHandle
import net.accelf.itc_lms_unofficial.coursedetail.CourseDetailActivity.Companion.EXTRA_COURSE_CONTENT_MATERIAL_ID
import net.accelf.itc_lms_unofficial.coursedetail.CourseDetailActivity.Companion.EXTRA_COURSE_ID
import net.accelf.itc_lms_unofficial.coursedetail.CourseDetailActivity.Companion.EXTRA_NOTIFY_ID
import net.accelf.itc_lms_unofficial.models.AttendanceSend
import net.accelf.itc_lms_unofficial.models.CourseDetail
import net.accelf.itc_lms_unofficial.models.NotifyDetail
import net.accelf.itc_lms_unofficial.network.LMS
import net.accelf.itc_lms_unofficial.util.Request
import net.accelf.itc_lms_unofficial.util.RxAwareViewModel
import net.accelf.itc_lms_unofficial.util.mutableLiveDataOf
import net.accelf.itc_lms_unofficial.util.mutableRequestOf
import net.accelf.itc_lms_unofficial.util.*

class CourseDetailViewModel @ViewModelInject constructor(
private val lms: LMS,
Expand All @@ -34,6 +32,9 @@ class CourseDetailViewModel @ViewModelInject constructor(
savedState.set(EXTRA_COURSE_CONTENT_MATERIAL_ID, value)
}

private val mutableAttendanceSend = mutableLiveDataOf<Request<AttendanceSend>?>(null)
val attendanceSend: LiveData<Request<AttendanceSend>?> = mutableAttendanceSend

init {
load()

Expand All @@ -48,8 +49,13 @@ class CourseDetailViewModel @ViewModelInject constructor(
.toLiveData(mutableCourseDetail)
}

private fun usingSnackbar(ignoreAttendance: Boolean = false): Boolean {
return mutableNotifyDetail.value != null
|| !(ignoreAttendance || mutableAttendanceSend.value == null)
}

fun loadNotify(notifyId: String): Boolean {
if (mutableNotifyDetail.value != null) {
if (usingSnackbar()) {
return false
}

Expand All @@ -67,4 +73,33 @@ class CourseDetailViewModel @ViewModelInject constructor(
fun onCourseContentOpened() {
focusCourseContentResourceId = null
}

fun prepareForSendingAttendance(attendanceId: String): Boolean {
if (usingSnackbar()) {
return false
}

lms.getAttendanceSend(courseId, attendanceId)
.toLiveData(mutableAttendanceSend)
return true
}

fun sendAttendance(password: String, comment: String): Boolean {
if (usingSnackbar(true)) {
return false
}

val form = (mutableAttendanceSend.value as Success).data
lms.sendAttendance(form.csrf, courseId, form.id, form.sent, password, comment)
.toLiveData(mutableAttendanceSend)
return true
}

fun closeSendAttendance() {
if (mutableAttendanceSend.value !is Success) {
return
}

mutableAttendanceSend.postValue(null)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package net.accelf.itc_lms_unofficial.coursedetail

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.setFragmentResult
import kotlinx.android.synthetic.main.dialog_send_attendance.view.*
import net.accelf.itc_lms_unofficial.R
import net.accelf.itc_lms_unofficial.util.Success
import net.accelf.itc_lms_unofficial.util.isNotNullOrEmpty

@SuppressLint("InflateParams")
class SendAttendanceDialogFragment : DialogFragment() {

private val layout by lazy {
layoutInflater.inflate(R.layout.dialog_send_attendance, null)
}

private val viewModel by activityViewModels<CourseDetailViewModel>()

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { activity ->
AlertDialog.Builder(activity).apply {
setTitle(R.string.dialog_title_send_attendance)
setView(layout)

setPositiveButton(R.string.button_dialog_send) { _, _ ->
setFragmentResult(
this@SendAttendanceDialogFragment::class.java.simpleName,
bundleOf(
BUNDLE_RESULT to Result.success(SendAttendanceDialogResult(
layout.editTextAttendancePassword.text.toString(),
layout.editTextAttendanceComment.text.toString()))
)
)
}
setNegativeButton(R.string.button_dialog_cancel) { _, _ ->
setFragmentResult(
this@SendAttendanceDialogFragment::class.java.simpleName,
bundleOf(BUNDLE_RESULT to Result.failure<SendAttendanceDialogResult>(
Throwable()))
)
}
}.create().apply {
setOnShowListener { dialog ->
val positiveButton =
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
positiveButton.isEnabled = false

(viewModel.attendanceSend.value as Success).data.let { attendanceSend ->
layout.textAttendanceAlreadySent.visibility = when (attendanceSend.sent) {
true -> VISIBLE
false -> GONE
}

layout.textAttendanceError.visibility =
when (attendanceSend.errorOnPassword.isNotEmpty() || attendanceSend.errorOnComment.isNotEmpty()) {
true -> VISIBLE
false -> GONE
}

// Warning: Using apply causes wrong `this` suggestion
layout.inputLayoutAttendancePassword.error = attendanceSend.errorOnPassword
layout.editTextAttendancePassword.setText(attendanceSend.password)
layout.editTextAttendancePassword.addTextChangedListener {
layout.inputLayoutAttendancePassword.isErrorEnabled = false
positiveButton.isEnabled = it.isNotNullOrEmpty()
}

layout.inputLayoutAttendanceComment.error = attendanceSend.errorOnComment
layout.editTextAttendanceComment.setText(attendanceSend.comment)
layout.editTextAttendanceComment.addTextChangedListener {
layout.inputLayoutAttendanceComment.isErrorEnabled = false
}
}
}
}
} ?: throw IllegalStateException("Activity cannot be null")
}

override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
viewModel.closeSendAttendance()
}

companion object {
const val BUNDLE_RESULT = "result"

fun newInstance(): SendAttendanceDialogFragment {
return SendAttendanceDialogFragment()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.accelf.itc_lms_unofficial.coursedetail

data class SendAttendanceDialogResult(
val attendancePassword: String,
val attendanceComment: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.accelf.itc_lms_unofficial.models

import net.accelf.itc_lms_unofficial.network.DocumentConverterFactory
import net.accelf.itc_lms_unofficial.util.popIf
import net.accelf.itc_lms_unofficial.util.toTimeSpan
import okhttp3.ResponseBody
import java.io.Serializable
import java.util.*

data class AttendanceSend(
val id: String,
val csrf: String,
val sent: Boolean,
val allowedSince: Date?,
val allowedUntil: Date?,
val lateSince: Date?,
val lateUntil: Date?,
val password: String,
val errorOnPassword: String,
val comment: String,
val errorOnComment: String,
val success: Boolean,
) : Serializable {

class Converter(baseUrl: String) :
DocumentConverterFactory.DocumentConverter<AttendanceSend>(baseUrl) {

override fun convert(value: ResponseBody): AttendanceSend? {
document(value).let { document ->
document.select("#attendancesSendForm").first().let { form ->
var allowedSince: Date? = null
var allowedUntil: Date? = null
var lateSince: Date? = null
var lateUntil: Date? = null
form?.select(".subblock_contents .subblock_line .subblock_form .important")
?.apply {
first().text().toTimeSpan().apply {
allowedSince = get(0)
allowedUntil = get(1)
}
last().text().toTimeSpan().apply {
lateSince = get(0)
lateUntil = get(1)
}
}

val passInput = form?.select("input[name=oneTimePass]")?.first()
val commentTextArea = form?.select("textarea[name=comment]")?.first()
val errors =
form?.select(".subblock_form .errorMsg")?.map { it.text() }?.toMutableList()

return AttendanceSend(
form?.select("input[name=attendanceId]")?.first()?.`val`() ?: "",
form?.select("input[name=_csrf]")?.first()?.`val`() ?: "",
(form?.select("input[name=isSent]")?.first()?.`val`() ?: "true") == "true",
allowedSince,
allowedUntil,
lateSince,
lateUntil,
passInput?.`val`() ?: "",
errors?.popIf { passInput?.hasClass("inputErrorField") ?: false } ?: "",
commentTextArea?.text() ?: "",
errors?.popIf { commentTextArea?.hasClass("inputErrorField") ?: false }
?: "",
document.select("#attendanceSendComplete").isNotEmpty(),
)
}
}
}
}
}
Loading

0 comments on commit dcea531

Please sign in to comment.