forked from github/wulkanowy-mirror
Add admin messages (#1553)
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.base
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||
@ -9,7 +10,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
|
||||
open class ErrorHandler @Inject constructor(@ApplicationContext protected val context: Context) {
|
||||
|
||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||
|
||||
@ -25,7 +26,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
|
||||
}
|
||||
|
||||
protected open fun proceed(error: Throwable) {
|
||||
showErrorMessage(resources.getString(error), error)
|
||||
showErrorMessage(context.resources.getString(error), error)
|
||||
when (error) {
|
||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.github.wulkanowy.ui.modules.dashboard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@ -18,6 +20,7 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
|
||||
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
|
||||
@ -63,6 +66,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
var onConferencesTileClickListener: () -> Unit = {}
|
||||
|
||||
var onAdminMessageClickListener: (String?) -> Unit = {}
|
||||
|
||||
val items = mutableListOf<DashboardItem>()
|
||||
|
||||
fun submitList(newItems: List<DashboardItem>) {
|
||||
@ -109,6 +114,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
|
||||
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
||||
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
@ -123,6 +131,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
||||
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
||||
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
||||
is AdminMessageViewHolder -> bindAdminMessage(holder, position)
|
||||
}
|
||||
}
|
||||
|
||||
@ -697,6 +706,34 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindAdminMessage(adminMessageViewHolder: AdminMessageViewHolder, position: Int) {
|
||||
val item = (items[position] as DashboardItem.AdminMessages).adminMessage ?: return
|
||||
val context = adminMessageViewHolder.binding.root.context
|
||||
val (backgroundColor, textColor) = when (item.priority) {
|
||||
"HIGH" -> {
|
||||
context.getThemeAttrColor(R.attr.colorPrimary) to
|
||||
context.getThemeAttrColor(R.attr.colorOnPrimary)
|
||||
}
|
||||
"MEDIUM" -> {
|
||||
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
|
||||
}
|
||||
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
|
||||
with(adminMessageViewHolder.binding) {
|
||||
dashboardAdminMessageItemTitle.text = item.title
|
||||
dashboardAdminMessageItemTitle.setTextColor(textColor)
|
||||
dashboardAdminMessageItemDescription.text = item.content
|
||||
dashboardAdminMessageItemDescription.setTextColor(textColor)
|
||||
dashboardAdminMessageItemIcon.setColorFilter(textColor)
|
||||
|
||||
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
||||
item.destinationUrl?.let { url ->
|
||||
root.setOnClickListener { onAdminMessageClickListener(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
@ -736,6 +773,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val adapter by lazy { DashboardConferencesAdapter() }
|
||||
}
|
||||
|
||||
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class DiffCallback(
|
||||
private val newList: List<DashboardItem>,
|
||||
private val oldList: List<DashboardItem>
|
||||
|
@ -10,6 +10,7 @@ import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.FragmentDashboardBinding
|
||||
@ -29,6 +30,7 @@ import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragm
|
||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
@ -97,6 +99,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
onConferencesTileClickListener = {
|
||||
mainActivity.pushView(ConferenceFragment.newInstance())
|
||||
}
|
||||
onAdminMessageClickListener = presenter::onAdminMessageSelected
|
||||
|
||||
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
binding.dashboardRecycler.scrollToPosition(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
@ -188,6 +197,10 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
(requireActivity() as MainActivity).pushView(NotificationsCenterFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openInternetBrowser(url: String) {
|
||||
requireContext().openInternetBrowser(url)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
dashboardAdapter.clearTimers()
|
||||
presenter.onDetachView()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.dashboard
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
@ -16,6 +17,15 @@ sealed class DashboardItem(val type: Type) {
|
||||
|
||||
abstract val isDataLoaded: Boolean
|
||||
|
||||
data class AdminMessages(
|
||||
val adminMessage: AdminMessage? = null,
|
||||
override val error: Throwable? = null,
|
||||
override val isLoading: Boolean = false
|
||||
) : DashboardItem(Type.ADMIN_MESSAGE) {
|
||||
|
||||
override val isDataLoaded get() = adminMessage != null
|
||||
}
|
||||
|
||||
data class Account(
|
||||
val student: Student? = null,
|
||||
override val error: Throwable? = null,
|
||||
@ -96,6 +106,7 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
ADMIN_MESSAGE,
|
||||
ACCOUNT,
|
||||
HORIZONTAL_GROUP,
|
||||
LESSONS,
|
||||
@ -108,6 +119,7 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
enum class Tile {
|
||||
ADMIN_MESSAGE,
|
||||
ACCOUNT,
|
||||
LUCKY_NUMBER,
|
||||
MESSAGES,
|
||||
@ -123,6 +135,7 @@ sealed class DashboardItem(val type: Type) {
|
||||
}
|
||||
|
||||
fun DashboardItem.Tile.toDashboardItemType() = when (this) {
|
||||
DashboardItem.Tile.ADMIN_MESSAGE -> DashboardItem.Type.ADMIN_MESSAGE
|
||||
DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT
|
||||
DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP
|
||||
DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP
|
||||
|
@ -21,7 +21,7 @@ class DashboardItemMoveCallback(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val dragFlags = if (viewHolder.bindingAdapterPosition != 0) {
|
||||
val dragFlags = if (!viewHolder.isAdminMessageOrAccountItem) {
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
} else 0
|
||||
|
||||
@ -32,7 +32,7 @@ class DashboardItemMoveCallback(
|
||||
recyclerView: RecyclerView,
|
||||
current: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
) = target.bindingAdapterPosition != 0
|
||||
) = !target.isAdminMessageOrAccountItem
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
@ -52,4 +52,7 @@ class DashboardItemMoveCallback(
|
||||
|
||||
onUserInteractionEndListener(dashboardAdapter.items.toList())
|
||||
}
|
||||
|
||||
private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean
|
||||
get() = this is DashboardAdapter.AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.AdminMessageRepository
|
||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||
import io.github.wulkanowy.data.repositories.ExamRepository
|
||||
@ -50,7 +51,8 @@ class DashboardPresenter @Inject constructor(
|
||||
private val examRepository: ExamRepository,
|
||||
private val conferenceRepository: ConferenceRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository
|
||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
||||
private val adminMessageRepository: AdminMessageRepository
|
||||
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
||||
|
||||
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
|
||||
@ -179,6 +181,7 @@ class DashboardPresenter @Inject constructor(
|
||||
loadConferences(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.ADS -> TODO()
|
||||
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,6 +228,10 @@ class DashboardPresenter @Inject constructor(
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
fun onAdminMessageSelected(url: String?) {
|
||||
url?.let { view?.openInternetBrowser(it) }
|
||||
}
|
||||
|
||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||
flow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
@ -567,6 +574,38 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_conferences")
|
||||
}
|
||||
|
||||
private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) }
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard admin message data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.AdminMessages(), forceRefresh)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard admin message result: Success")
|
||||
updateData(
|
||||
dashboardItem = DashboardItem.AdminMessages(adminMessage = it.data),
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard admin message result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(
|
||||
dashboardItem = DashboardItem.AdminMessages(
|
||||
adminMessage = it.data,
|
||||
error = it.error
|
||||
),
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launch("dashboard_admin_messages")
|
||||
}
|
||||
|
||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||
val isFirstRunDataLoadedError =
|
||||
@ -579,6 +618,11 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
sortDashboardItems()
|
||||
|
||||
if (dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded) {
|
||||
dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE
|
||||
dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE }
|
||||
}
|
||||
|
||||
if (forceRefresh) {
|
||||
updateForceRefreshData(dashboardItem)
|
||||
} else {
|
||||
@ -644,7 +688,9 @@ class DashboardPresenter @Inject constructor(
|
||||
itemsLoadedList: List<DashboardItem>,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val filteredItems = itemsLoadedList.filterNot {
|
||||
it.type == DashboardItem.Type.ACCOUNT || it.type == DashboardItem.Type.ADMIN_MESSAGE
|
||||
}
|
||||
val isAccountItemError =
|
||||
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||
val isGeneralError =
|
||||
@ -676,10 +722,13 @@ class DashboardPresenter @Inject constructor(
|
||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||
|
||||
dashboardItemLoadedList.sortBy { tile ->
|
||||
dashboardItemsPosition?.getOrDefault(
|
||||
tile.type,
|
||||
val defaultPosition = if (tile is DashboardItem.AdminMessages) {
|
||||
-1
|
||||
} else {
|
||||
tile.type.ordinal + 100
|
||||
) ?: tile.type.ordinal
|
||||
}
|
||||
|
||||
dashboardItemsPosition?.getOrDefault(tile.type, defaultPosition) ?: tile.type.ordinal
|
||||
}
|
||||
}
|
||||
}
|
@ -25,4 +25,6 @@ interface DashboardView : BaseView {
|
||||
fun popViewToRoot()
|
||||
|
||||
fun openNotificationsCenterView()
|
||||
|
||||
fun openInternetBrowser(url: String)
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package io.github.wulkanowy.ui.modules.login
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.sdk.mobile.exception.InvalidPinException
|
||||
import io.github.wulkanowy.sdk.mobile.exception.InvalidSymbolException
|
||||
@ -11,7 +12,8 @@ import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
class LoginErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||
ErrorHandler(context) {
|
||||
|
||||
var onBadCredentials: (String?) -> Unit = {}
|
||||
|
||||
@ -24,6 +26,7 @@ class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler
|
||||
var onStudentDuplicate: (String) -> Unit = {}
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
val resources = context.resources
|
||||
when (error) {
|
||||
is BadCredentialsException -> onBadCredentials(error.message)
|
||||
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
|
||||
|
@ -1,13 +1,15 @@
|
||||
package io.github.wulkanowy.ui.modules.login.recover
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.InvalidCaptchaException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.InvalidEmailException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.NoAccountFoundException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
class RecoverErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||
ErrorHandler(context) {
|
||||
|
||||
var onInvalidUsername: (String) -> Unit = {}
|
||||
|
||||
@ -15,7 +17,8 @@ class RecoverErrorHandler @Inject constructor(resources: Resources) : ErrorHandl
|
||||
|
||||
override fun proceed(error: Throwable) {
|
||||
when (error) {
|
||||
is InvalidEmailException, is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty())
|
||||
is InvalidEmailException,
|
||||
is NoAccountFoundException -> onInvalidUsername(error.localizedMessage.orEmpty())
|
||||
is InvalidCaptchaException -> onInvalidCaptcha(error.localizedMessage.orEmpty(), error)
|
||||
else -> super.proceed(error)
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable.completed
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
class CompletedLessonsErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) {
|
||||
class CompletedLessonsErrorHandler @Inject constructor(@ApplicationContext context: Context) :
|
||||
ErrorHandler(context) {
|
||||
|
||||
var onFeatureDisabled: () -> Unit = {}
|
||||
|
||||
|
Reference in New Issue
Block a user