1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-11-22 10:46:32 -06:00

Add admin messages to login screen (#2280)

This commit is contained in:
Mikołaj Pich 2023-08-24 11:33:40 +02:00 committed by GitHub
parent fbce9e58d0
commit 3dfc55c4d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2722 additions and 85 deletions

View File

@ -27,7 +27,7 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21
targetSdkVersion 33
versionCode 130
versionCode 131 // todo: already bumped for 2.1.0 version
versionName "2.0.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

File diff suppressed because it is too large Load Diff

View File

@ -148,7 +148,7 @@ inline fun <ResultType, RequestType, T> networkBoundResource(
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline mapResult: (ResultType) -> T
crossinline mapResult: (ResultType) -> T,
) = flow {
emit(Resource.Loading())

View File

@ -50,6 +50,7 @@ import javax.inject.Singleton
AutoMigration(from = 51, to = 52),
AutoMigration(from = 54, to = 55, spec = Migration55::class),
AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57, spec = Migration57::class),
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -58,7 +59,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 56
const val VERSION_SCHEMA = 57
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.db
import androidx.room.TypeConverter
import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException
@ -68,4 +69,9 @@ class Converters {
@TypeConverter
fun stringToDestination(destination: String): Destination = json.decodeFromString(destination)
@TypeConverter
fun messageTypesToString(types: List<MessageType>): String = json.encodeToString(types)
@TypeConverter
fun stringToMessageTypes(text: String): List<MessageType> = json.decodeFromString(text)
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.github.wulkanowy.data.enums.MessageType
import kotlinx.serialization.Serializable
@Serializable
@ -33,7 +34,8 @@ data class AdminMessage(
val priority: String,
val type: String,
@ColumnInfo(name = "types", defaultValue = "[]")
val types: List<MessageType> = emptyList(),
@ColumnInfo(name = "is_dismissible")
val isDismissible: Boolean = false

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.DeleteColumn
import androidx.room.migration.AutoMigrationSpec
@DeleteColumn(
tableName = "AdminMessages",
columnName = "type",
)
class Migration57 : AutoMigrationSpec

View File

@ -0,0 +1,9 @@
package io.github.wulkanowy.data.enums
enum class MessageType {
GENERAL_MESSAGE,
DASHBOARD_MESSAGE,
LOGIN_MESSAGE,
PASS_RESET_MESSAGE,
ERROR_OVERRIDE,
}

View File

@ -1,10 +1,11 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@ -13,34 +14,20 @@ import javax.inject.Singleton
class AdminMessageRepository @Inject constructor(
private val adminMessageService: AdminMessageService,
private val adminMessageDao: AdminMessageDao,
private val appInfo: AppInfo
) {
private val saveFetchResultMutex = Mutex()
suspend fun getAdminMessages(student: Student) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true },
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
},
showSavedOnLoading = false,
mapResult = { adminMessages ->
adminMessages.filter { adminMessage ->
val isCorrectRegister = adminMessage.targetRegisterHost?.let {
student.scrapperBaseUrl.contains(it, true)
} ?: true
val isCorrectFlavor =
adminMessage.targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
val isCorrectMaxVersion =
adminMessage.versionMax?.let { it >= appInfo.versionCode } ?: true
val isCorrectMinVersion =
adminMessage.versionMin?.let { it <= appInfo.versionCode } ?: true
isCorrectRegister && isCorrectFlavor && isCorrectMaxVersion && isCorrectMinVersion
}.maxByOrNull { it.id }
}
)
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { false },
query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true },
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
},
showSavedOnLoading = false,
)
}

View File

@ -0,0 +1,64 @@
package io.github.wulkanowy.domain.adminmessage
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.data.mapResourceData
import io.github.wulkanowy.data.repositories.AdminMessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class GetAppropriateAdminMessageUseCase @Inject constructor(
private val adminMessageRepository: AdminMessageRepository,
private val preferencesRepository: PreferencesRepository,
private val appInfo: AppInfo
) {
operator fun invoke(student: Student, type: MessageType): Flow<Resource<AdminMessage?>> {
return invoke(student.scrapperBaseUrl, type)
}
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
adminMessages
.asSequence()
.filter { it.isNotDismissed() }
.filter { it.isVersionMatch() }
.filter { it.isRegisterHostMatch(scrapperBaseUrl) }
.filter { it.isFlavorMatch() }
.filter { it.isTypeMatch(type) }
.maxByOrNull { it.id }
}
}
private fun AdminMessage.isNotDismissed(): Boolean {
return id !in preferencesRepository.dismissedAdminMessageIds
}
private fun AdminMessage.isRegisterHostMatch(scrapperBaseUrl: String): Boolean {
return targetRegisterHost?.let {
scrapperBaseUrl.contains(it, true)
} ?: true
}
private fun AdminMessage.isFlavorMatch(): Boolean {
return targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
}
private fun AdminMessage.isVersionMatch(): Boolean {
val isCorrectMaxVersion = versionMax?.let { it >= appInfo.versionCode } ?: true
val isCorrectMinVersion = versionMin?.let { it <= appInfo.versionCode } ?: true
return isCorrectMaxVersion && isCorrectMinVersion
}
private fun AdminMessage.isTypeMatch(messageType: MessageType): Boolean {
if (messageType in types) return true
if (MessageType.GENERAL_MESSAGE in types) return true
return false
}
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.dashboard
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import java.util.*
class DashboardItemMoveCallback(
@ -55,5 +56,5 @@ class DashboardItemMoveCallback(
}
private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean
get() = this is DashboardAdapter.AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
get() = this is AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
}

View File

@ -5,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
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.enums.MessageType
import io.github.wulkanowy.data.repositories.*
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AdsHelper
@ -32,7 +34,7 @@ class DashboardPresenter @Inject constructor(
private val conferenceRepository: ConferenceRepository,
private val preferencesRepository: PreferencesRepository,
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
private val adminMessageRepository: AdminMessageRepository,
private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase,
private val adsHelper: AdsHelper
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
@ -159,19 +161,23 @@ class DashboardPresenter @Inject constructor(
DashboardItem.Type.ACCOUNT -> {
updateData(DashboardItem.Account(student), forceRefresh)
}
DashboardItem.Type.HORIZONTAL_GROUP -> {
loadHorizontalGroup(student, forceRefresh)
}
DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh)
DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh)
DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh)
DashboardItem.Type.ANNOUNCEMENTS -> {
loadSchoolAnnouncements(student, forceRefresh)
}
DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh)
DashboardItem.Type.CONFERENCES -> {
loadConferences(student, forceRefresh)
}
DashboardItem.Type.ADS -> loadAds(forceRefresh)
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
}
@ -355,6 +361,7 @@ class DashboardPresenter @Inject constructor(
firstLoadedItemList += DashboardItem.Type.GRADES
}
}
is Resource.Success -> {
Timber.i("Loading dashboard grades result: Success")
updateData(
@ -365,6 +372,7 @@ class DashboardPresenter @Inject constructor(
forceRefresh
)
}
is Resource.Error -> {
Timber.i("Loading dashboard grades result: An exception occurred")
errorHandler.dispatch(it.error)
@ -402,12 +410,14 @@ class DashboardPresenter @Inject constructor(
firstLoadedItemList += DashboardItem.Type.LESSONS
}
}
is Resource.Success -> {
Timber.i("Loading dashboard lessons result: Success")
updateData(
DashboardItem.Lessons(it.data), forceRefresh
)
}
is Resource.Error -> {
Timber.i("Loading dashboard lessons result: An exception occurred")
errorHandler.dispatch(it.error)
@ -457,10 +467,12 @@ class DashboardPresenter @Inject constructor(
firstLoadedItemList += DashboardItem.Type.HOMEWORK
}
}
is Resource.Success -> {
Timber.i("Loading dashboard homework result: Success")
updateData(DashboardItem.Homework(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard homework result: An exception occurred")
errorHandler.dispatch(it.error)
@ -489,10 +501,12 @@ class DashboardPresenter @Inject constructor(
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
}
}
is Resource.Success -> {
Timber.i("Loading dashboard announcements result: Success")
updateData(DashboardItem.Announcements(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard announcements result: An exception occurred")
errorHandler.dispatch(it.error)
@ -530,10 +544,12 @@ class DashboardPresenter @Inject constructor(
firstLoadedItemList += DashboardItem.Type.EXAMS
}
}
is Resource.Success -> {
Timber.i("Loading dashboard exams result: Success")
updateData(DashboardItem.Exams(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard exams result: An exception occurred")
errorHandler.dispatch(it.error)
@ -569,10 +585,12 @@ class DashboardPresenter @Inject constructor(
firstLoadedItemList += DashboardItem.Type.CONFERENCES
}
}
is Resource.Success -> {
Timber.i("Loading dashboard conferences result: Success")
updateData(DashboardItem.Conferences(it.data), forceRefresh)
}
is Resource.Error -> {
Timber.i("Loading dashboard conferences result: An exception occurred")
errorHandler.dispatch(it.error)
@ -584,12 +602,12 @@ class DashboardPresenter @Inject constructor(
}
private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
flatResourceFlow { adminMessageRepository.getAdminMessages(student) }
.filter {
val data = it.dataOrNull ?: return@filter true
val isDismissed = data.id in preferencesRepository.dismissedAdminMessageIds
!isDismissed
}
flatResourceFlow {
getAppropriateAdminMessageUseCase(
student = student,
type = MessageType.DASHBOARD_MESSAGE,
)
}
.onEach {
when (it) {
is Resource.Loading -> {
@ -597,6 +615,7 @@ class DashboardPresenter @Inject constructor(
if (forceRefresh) return@onEach
updateData(DashboardItem.AdminMessages(), forceRefresh)
}
is Resource.Success -> {
Timber.i("Loading dashboard admin message result: Success")
updateData(
@ -604,6 +623,7 @@ class DashboardPresenter @Inject constructor(
forceRefresh = forceRefresh
)
}
is Resource.Error -> {
Timber.i("Loading dashboard admin message result: An exception occurred")
Timber.e(it.error)

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.ui.modules.dashboard.adapters
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
@ -24,6 +22,7 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.utils.*
import timber.log.Timber
import java.time.Duration
@ -109,7 +108,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false),
onAdminMessageDismissClickListener = onAdminMessageDismissClickListener,
onAdminMessageClickListener = onAdminMessageClickListener,
)
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
ItemDashboardAdsBinding.inflate(inflater, parent, false)
@ -128,7 +129,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)
is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage)
is AdsViewHolder -> bindAdsViewHolder(holder, position)
}
}
@ -733,39 +734,6 @@ 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.colorMessageHigh) to
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
}
"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)
dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
dashboardAdminMessageItemDismiss.setTextColor(textColor)
dashboardAdminMessageItemDismiss.setOnClickListener {
onAdminMessageDismissClickListener(item)
}
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url ->
root.setOnClickListener { onAdminMessageClickListener(url) }
}
}
}
private fun bindAdsViewHolder(adsViewHolder: AdsViewHolder, position: Int) {
val item = (items[position] as DashboardItem.Ads).adBanner ?: return
val binding = adsViewHolder.binding
@ -819,9 +787,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
val adapter by lazy { DashboardConferencesAdapter() }
}
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
RecyclerView.ViewHolder(binding.root)
class AdsViewHolder(val binding: ItemDashboardAdsBinding) :
RecyclerView.ViewHolder(binding.root)

View File

@ -0,0 +1,52 @@
package io.github.wulkanowy.ui.modules.dashboard.viewholders
import android.content.res.ColorStateList
import android.graphics.Color
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.utils.getThemeAttrColor
class AdminMessageViewHolder(
private val binding: ItemDashboardAdminMessageBinding,
private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit,
private val onAdminMessageClickListener: (String?) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: AdminMessage?) {
item ?: return
val context = binding.root.context
val (backgroundColor, textColor) = when (item.priority) {
"HIGH" -> {
context.getThemeAttrColor(R.attr.colorMessageHigh) to
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
}
"MEDIUM" -> {
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
}
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
}
with(binding) {
dashboardAdminMessageItemTitle.text = item.title
dashboardAdminMessageItemTitle.setTextColor(textColor)
dashboardAdminMessageItemDescription.text = item.content
dashboardAdminMessageItemDescription.setTextColor(textColor)
dashboardAdminMessageItemIcon.setColorFilter(textColor)
dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
dashboardAdminMessageItemDismiss.setTextColor(textColor)
dashboardAdminMessageItemDismiss.setOnClickListener {
onAdminMessageDismissClickListener(item)
}
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url ->
root.setOnClickListener { onAdminMessageClickListener(url) }
}
}
}
}

View File

@ -9,10 +9,12 @@ import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.utils.*
@ -207,6 +209,19 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
binding.loginFormContainer.visibility = if (show) VISIBLE else GONE
}
override fun showAdminMessage(message: AdminMessage?) {
AdminMessageViewHolder(
binding = binding.loginFormMessage,
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
onAdminMessageClickListener = presenter::onAdminMessageSelected,
).bind(message)
binding.loginFormMessage.root.isVisible = message != null
}
override fun openInternetBrowser(url: String) {
requireContext().openInternetBrowser(url)
}
override fun showDomainSuffixInput(show: Boolean) {
binding.loginFormDomainSuffixLayout.isVisible = show
}

View File

@ -1,13 +1,19 @@
package io.github.wulkanowy.ui.modules.login.form
import androidx.core.net.toUri
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.data.flatResourceFlow
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceData
import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceLoading
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@ -22,7 +28,9 @@ class LoginFormPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val appInfo: AppInfo,
private val analytics: AnalyticsHelper
private val analytics: AnalyticsHelper,
private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase,
private val preferencesRepository: PreferencesRepository,
) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null
@ -41,6 +49,31 @@ class LoginFormPresenter @Inject constructor(
Timber.i("Entered wrong username or password")
}
}
reloadAdminMessage()
}
private fun reloadAdminMessage() {
flatResourceFlow {
getAppropriateAdminMessageUseCase(
scrapperBaseUrl = view?.formHostValue.orEmpty(),
type = MessageType.LOGIN_MESSAGE,
)
}
.logResourceStatus("load login admin message")
.onResourceData { view?.showAdminMessage(it) }
.onResourceError { view?.showAdminMessage(null) }
.launch()
}
fun onAdminMessageSelected(url: String?) {
url?.let { view?.openInternetBrowser(it) }
}
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
view?.showAdminMessage(null)
}
fun onPrivacyLinkClick() {
@ -63,6 +96,7 @@ class LoginFormPresenter @Inject constructor(
}
updateCustomDomainSuffixVisibility()
updateUsernameLabel()
reloadAdminMessage()
}
}
@ -103,7 +137,9 @@ class LoginFormPresenter @Inject constructor(
val email = view?.formUsernameValue.orEmpty().trim()
val password = view?.formPassValue.orEmpty().trim()
val host = view?.formHostValue.orEmpty().trim()
val domainSuffix = view?.formDomainSuffix.orEmpty().trim()
val domainSuffix = view?.formDomainSuffix.orEmpty().trim().takeIf {
"customSuffix" in host
}.orEmpty()
val symbol = view?.formHostSymbol.orEmpty().trim()
if (!validateCredentials(email, password, host)) return

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
@ -58,6 +59,10 @@ interface LoginFormView : BaseView {
fun showContent(show: Boolean)
fun showAdminMessage(message: AdminMessage?)
fun openInternetBrowser(url: String)
fun showDomainSuffixInput(show: Boolean)
fun showOtherOptionsButton(show: Boolean)

View File

@ -105,6 +105,18 @@
android:background="?android:attr/listDivider" />
</LinearLayout>
<include
android:id="@+id/login_form_message"
layout="@layout/item_dashboard_admin_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/loginFormContact"
app:layout_constraintVertical_chainStyle="packed"
tools:visibility="visible" />
<TextView
android:id="@+id/loginFormHeader"
android:layout_width="match_parent"
@ -119,9 +131,8 @@
app:layout_constraintBottom_toTopOf="@+id/loginFormUsernameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginFormContact"
app:layout_constraintTop_toBottomOf="@+id/login_form_message"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginTop="64dp" />
<TextView

View File

@ -2,7 +2,9 @@ package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.MainCoroutineRule
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.scrapper.Scrapper
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@ -40,6 +42,12 @@ class LoginFormPresenterTest {
@MockK
lateinit var appInfo: AppInfo
@MockK
lateinit var getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase
@MockK
lateinit var preferencesRepository: PreferencesRepository
private lateinit var presenter: LoginFormPresenter
private val registerUser = RegisterUser(
@ -72,6 +80,8 @@ class LoginFormPresenterTest {
loginErrorHandler = errorHandler,
appInfo = appInfo,
analytics = analytics,
getAppropriateAdminMessageUseCase = getAppropriateAdminMessageUseCase,
preferencesRepository = preferencesRepository,
)
presenter.onAttachView(loginFormView)
}