Refactor student selection screen (#2087)

This commit is contained in:
Mikołaj Pich 2023-01-01 20:26:32 +01:00 committed by GitHub
parent 83974b6550
commit 897eac050a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1052 additions and 349 deletions

View File

@ -186,7 +186,7 @@ ext {
}
dependencies {
implementation "io.github.wulkanowy:sdk:1.8.3"
implementation "io.github.wulkanowy:sdk:a3b97edd48"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'

View File

@ -0,0 +1,87 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.*
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.mapper.mapSemesters
import java.time.Instant
import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent as SdkRegisterStudent
import io.github.wulkanowy.sdk.scrapper.register.RegisterUser as SdkRegisterUser
fun SdkRegisterUser.mapToPojo(password: String) = RegisterUser(
email = email,
login = login,
password = password,
baseUrl = baseUrl,
loginType = loginType,
symbols = symbols.map { registerSymbol ->
RegisterSymbol(
symbol = registerSymbol.symbol,
error = registerSymbol.error,
userName = registerSymbol.userName,
schools = registerSymbol.schools.map {
RegisterUnit(
userLoginId = it.userLoginId,
schoolId = it.schoolId,
schoolName = it.schoolName,
schoolShortName = it.schoolShortName,
parentIds = it.parentIds,
studentIds = it.studentIds,
employeeIds = it.employeeIds,
error = it.error,
students = it.subjects
.filterIsInstance<SdkRegisterStudent>()
.map { registerSubject ->
RegisterStudent(
studentId = registerSubject.studentId,
studentName = registerSubject.studentName,
studentSecondName = registerSubject.studentSecondName,
studentSurname = registerSubject.studentSurname,
className = registerSubject.className,
classId = registerSubject.classId,
isParent = registerSubject.isParent,
semesters = registerSubject.semesters
.mapSemesters()
.mapToEntities(registerSubject.studentId),
)
},
)
}
)
}
)
fun RegisterStudent.mapToStudentWithSemesters(
user: RegisterUser,
symbol: RegisterSymbol,
unit: RegisterUnit,
colors: List<Long>,
): StudentWithSemesters = StudentWithSemesters(
semesters = semesters,
student = Student(
email = user.login, // for compatibility
userName = symbol.userName,
userLoginId = unit.userLoginId,
isParent = isParent,
className = className,
classId = classId,
studentId = studentId,
symbol = symbol.symbol,
loginType = user.loginType.name,
schoolName = unit.schoolName,
schoolShortName = unit.schoolShortName,
schoolSymbol = unit.schoolId,
studentName = "$studentName $studentSurname",
loginMode = Sdk.Mode.SCRAPPER.name,
scrapperBaseUrl = user.baseUrl,
mobileBaseUrl = "",
certificateKey = "",
privateKey = "",
password = user.password,
isCurrent = false,
registrationDate = Instant.now(),
).apply {
avatarColor = colors.random()
},
)

View File

@ -0,0 +1,43 @@
package io.github.wulkanowy.data.pojos
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.sdk.scrapper.Scrapper
data class RegisterUser(
val email: String,
val password: String,
val login: String, // may be the same as email
val baseUrl: String,
val loginType: Scrapper.LoginType,
val symbols: List<RegisterSymbol>,
) : java.io.Serializable
data class RegisterSymbol(
val symbol: String,
val error: Throwable?,
val userName: String,
val schools: List<RegisterUnit>,
) : java.io.Serializable
data class RegisterUnit(
val userLoginId: Int,
val schoolId: String,
val schoolName: String,
val schoolShortName: String,
val parentIds: List<Int>,
val studentIds: List<Int>,
val employeeIds: List<Int>,
val error: Throwable?,
val students: List<RegisterStudent>,
) : java.io.Serializable
data class RegisterStudent(
val studentId: Int,
val studentName: String,
val studentSecondName: String,
val studentSurname: String,
val className: String,
val classId: Int,
val isParent: Boolean,
val semesters: List<Semester>,
) : java.io.Serializable

View File

@ -11,6 +11,8 @@ import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToPojo
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.DispatchersProvider
@ -52,6 +54,14 @@ class StudentRepository @Inject constructor(
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToEntities(password, appInfo.defaultColorsForAvatar)
suspend fun getUserSubjectsFromScrapper(
email: String,
password: String,
scrapperBaseUrl: String,
symbol: String
): RegisterUser = sdk.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToPojo(password)
suspend fun getStudentsHybrid(
email: String,
password: String,

View File

@ -8,10 +8,11 @@ import android.os.Bundle
import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.databinding.ActivityLoginBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.advanced.LoginAdvancedFragment
@ -76,8 +77,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
openFragment(LoginSymbolFragment.newInstance(loginData))
}
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters))
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
openFragment(LoginStudentSelectFragment.newInstance(loginData, registerUser))
}
fun navigateToNotifications() {
@ -105,6 +106,8 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
}
private fun openFragment(fragment: Fragment, clearBackStack: Boolean = false) {
supportFragmentManager.popBackStack(fragment::class.java.name, POP_BACK_STACK_INCLUSIVE)
supportFragmentManager.commit {
replace(R.id.loginContainer, fragment)
setReorderingAllowed(true)

View File

@ -6,4 +6,5 @@ data class LoginData(
val login: String,
val password: String,
val baseUrl: String,
val symbol: String?,
) : Serializable

View File

@ -8,7 +8,7 @@ import android.widget.ArrayAdapter
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.databinding.FragmentLoginAdvancedBinding
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BaseFragment
@ -327,8 +327,8 @@ class LoginAdvancedFragment :
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
}
override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
(activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
}
override fun onResume() {

View File

@ -4,9 +4,15 @@ import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.pojos.RegisterStudent
import io.github.wulkanowy.data.pojos.RegisterSymbol
import io.github.wulkanowy.data.pojos.RegisterUnit
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.scrapper.Scrapper
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@ -142,19 +148,23 @@ class LoginAdvancedPresenter @Inject constructor(
is Resource.Success -> {
analytics.logEvent(
"registration_form",
"success" to true,
"students" to it.data.size,
"error" to "No error"
)
val loginData = LoginData(
login = view?.formUsernameValue.orEmpty().trim(),
password = view?.formPassValue.orEmpty().trim(),
baseUrl = view?.formHostValue.orEmpty().trim()
)
when (it.data.size) {
0 -> view?.navigateToSymbol(loginData)
else -> view?.navigateToStudentSelect(it.data)
}
"success" to true,
"students" to it.data.size,
"error" to "No error"
)
val loginData = LoginData(
login = view?.formUsernameValue.orEmpty().trim(),
password = view?.formPassValue.orEmpty().trim(),
baseUrl = view?.formHostValue.orEmpty().trim(),
symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
)
when (it.data.size) {
0 -> view?.navigateToSymbol(loginData)
else -> view?.navigateToStudentSelect(
loginData = loginData,
registerUser = it.data.toRegisterUser(loginData),
)
}
}
is Resource.Error -> {
analytics.logEvent(
@ -173,6 +183,58 @@ class LoginAdvancedPresenter @Inject constructor(
}.launch("login")
}
private fun List<StudentWithSemesters>.toRegisterUser(loginData: LoginData) = RegisterUser(
email = loginData.login,
password = loginData.password,
login = loginData.login,
baseUrl = loginData.baseUrl,
loginType = firstOrNull()?.student?.loginType?.let(
Scrapper.LoginType::valueOf
) ?: Scrapper.LoginType.AUTO,
symbols = this
.groupBy { students -> students.student.symbol }
.map { (symbol, students) ->
RegisterSymbol(
symbol = symbol,
error = null,
userName = "",
schools = students
.groupBy { student ->
Triple(
first = student.student.schoolSymbol,
second = student.student.userLoginId,
third = student.student.schoolShortName
)
}
.map { (groupKey, students) ->
val (schoolId, loginId, schoolName) = groupKey
RegisterUnit(
students = students.map {
RegisterStudent(
studentId = it.student.studentId,
studentName = it.student.studentName,
studentSecondName = it.student.studentName,
studentSurname = it.student.studentName,
className = it.student.className,
classId = it.student.classId,
isParent = it.student.isParent,
semesters = it.semesters,
)
},
userLoginId = loginId,
schoolId = schoolId,
schoolName = schoolName,
schoolShortName = schoolName,
parentIds = listOf(),
studentIds = listOf(),
employeeIds = listOf(),
error = null
)
}
)
},
)
private suspend fun getStudentsAppropriatesToLoginType(): List<StudentWithSemesters> {
val email = view?.formUsernameValue.orEmpty()
val password = view?.formPassValue.orEmpty()

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.login.advanced
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
@ -72,7 +73,7 @@ interface LoginAdvancedView : BaseView {
fun navigateToSymbol(loginData: LoginData)
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
fun setErrorPinRequired()

View File

@ -9,7 +9,7 @@ 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.StudentWithSemesters
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
@ -226,8 +226,8 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
(activity as? LoginActivity)?.navigateToSymbolFragment(loginData)
}
override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
(activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
}
override fun openAdvancedLogin() {

View File

@ -93,7 +93,7 @@ class LoginFormPresenter @Inject constructor(
if (!validateCredentials(email, password, host)) return
resourceFlow {
studentRepository.getStudentsScrapper(
studentRepository.getUserSubjectsFromScrapper(
email = email,
password = password,
scrapperBaseUrl = host,
@ -109,14 +109,14 @@ class LoginFormPresenter @Inject constructor(
}
}
.onResourceSuccess {
when (it.size) {
0 -> view?.navigateToSymbol(LoginData(email, password, host))
else -> view?.navigateToStudentSelect(it)
val loginData = LoginData(email, password, host, symbol)
when (it.symbols.size) {
0 -> view?.navigateToSymbol(loginData)
else -> view?.navigateToStudentSelect(loginData, it)
}
analytics.logEvent(
"registration_form",
"success" to true,
"students" to it.size,
"scrapperBaseUrl" to host,
"error" to "No error"
)
@ -134,7 +134,6 @@ class LoginFormPresenter @Inject constructor(
analytics.logEvent(
"registration_form",
"success" to false,
"students" to -1,
"scrapperBaseUrl" to host,
"error" to it.message.ifNullOrBlank { "No message" }
)

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
@ -60,7 +60,7 @@ interface LoginFormView : BaseView {
fun navigateToSymbol(loginData: LoginData)
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
fun openPrivacyPolicyPage()

View File

@ -2,65 +2,182 @@ package io.github.wulkanowy.ui.modules.login.studentselect
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.*
import javax.inject.Inject
@SuppressLint("SetTextI18n")
class LoginStudentSelectAdapter @Inject constructor() :
RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() {
ListAdapter<LoginStudentSelectItem, RecyclerView.ViewHolder>(Differ) {
private val checkedList = mutableMapOf<Int, Boolean>()
override fun getItemViewType(position: Int): Int = getItem(position).type.ordinal
var items = emptyList<Pair<StudentWithSemesters, Boolean>>()
set(value) {
field = value
checkedList.clear()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (LoginStudentSelectItemType.values()[viewType]) {
LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER -> EmptySymbolsHeaderViewHolder(
ItemLoginStudentSelectEmptySymbolHeaderBinding.inflate(inflater, parent, false),
)
LoginStudentSelectItemType.SYMBOL_HEADER -> SymbolsHeaderViewHolder(
ItemLoginStudentSelectHeaderSymbolBinding.inflate(inflater, parent, false)
)
LoginStudentSelectItemType.SCHOOL_HEADER -> SchoolHeaderViewHolder(
ItemLoginStudentSelectHeaderSchoolBinding.inflate(inflater, parent, false)
)
LoginStudentSelectItemType.STUDENT -> StudentViewHolder(
ItemLoginStudentSelectStudentBinding.inflate(inflater, parent, false)
)
LoginStudentSelectItemType.HELP -> HelpViewHolder(
ItemLoginStudentSelectHelpBinding.inflate(inflater, parent, false)
)
}
}
var onClickListener: (StudentWithSemesters, alreadySaved: Boolean) -> Unit = { _, _ -> }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is EmptySymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.EmptySymbolsHeader)
is SymbolsHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SymbolHeader)
is SchoolHeaderViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.SchoolHeader)
is StudentViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Student)
is HelpViewHolder -> holder.bind(getItem(position) as LoginStudentSelectItem.Help)
}
}
override fun getItemCount() = items.size
private class EmptySymbolsHeaderViewHolder(
private val binding: ItemLoginStudentSelectEmptySymbolHeaderBinding,
) : RecyclerView.ViewHolder(binding.root) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (studentAndSemesters, alreadySaved) = items[position]
val student = studentAndSemesters.student
val semesters = studentAndSemesters.semesters
val diary = semesters.maxByOrNull { it.semesterId }
with(holder.binding) {
loginItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
loginItemSchool.text = student.schoolName
loginItemName.isEnabled = !alreadySaved
loginItemSchool.isEnabled = !alreadySaved
loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
with(loginItemCheck) {
isEnabled = !alreadySaved
keyListener = null
isChecked = checkedList[position] ?: false
fun bind(item: LoginStudentSelectItem.EmptySymbolsHeader) {
with(binding) {
loginStudentSelectEmptySymbolChevron.rotation = if (item.isExpanded) 270f else 90f
root.setOnClickListener { item.onClick() }
}
}
}
root.setOnClickListener {
onClickListener(studentAndSemesters, alreadySaved)
private class SymbolsHeaderViewHolder(
private val binding: ItemLoginStudentSelectHeaderSymbolBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.SymbolHeader) {
with(binding) {
loginStudentSelectHeaderSymbolValue.text = buildString {
append(root.context.getString(R.string.mobile_device_symbol))
append(": ")
append(item.humanReadableName ?: item.symbol.symbol)
if (!item.humanReadableName.isNullOrBlank()) {
append(" (${item.symbol.symbol})")
}
}
loginStudentSelectHeaderSymbolUsername.text = item.symbol.userName
loginStudentSelectHeaderSymbolUsername.isVisible = item.symbol.userName.isNotBlank()
loginStudentSelectHeaderSymbolError.text = item.symbol.error?.message
loginStudentSelectHeaderSymbolError.isVisible = item.symbol.error != null
loginStudentSelectHeaderSymbolError.maxLines = when {
item.isErrorExpanded -> Int.MAX_VALUE
else -> 2
}
if (item.symbol.error != null) {
root.setOnClickListener { item.onClick(item.symbol) }
} else root.setOnClickListener(null)
}
}
}
private class SchoolHeaderViewHolder(
private val binding: ItemLoginStudentSelectHeaderSchoolBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.SchoolHeader) {
with(binding) {
loginStudentSelectHeaderSchoolName.text = buildString {
append(item.unit.schoolName.trim())
append(" (")
append(item.unit.schoolShortName)
append(")")
}
loginStudentSelectHeaderSchoolDetails.isVisible = item.unit.students.isEmpty()
loginStudentSelectHeaderSchoolError.text = item.unit.error?.message
loginStudentSelectHeaderSchoolError.isVisible = item.unit.error != null
loginStudentSelectHeaderSchoolError.maxLines = when {
item.isErrorExpanded -> Int.MAX_VALUE
else -> 2
}
if (item.unit.error != null) {
root.setOnClickListener { item.onClick(item.unit) }
} else root.setOnClickListener(null)
}
}
}
private class StudentViewHolder(
private val binding: ItemLoginStudentSelectStudentBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.Student) {
val student = item.student
val semesters = student.semesters
val diary = semesters.maxByOrNull { it.semesterId }
with(binding) {
loginItemName.text = "${student.studentName} ${student.studentSurname}"
loginItemName.isEnabled = item.isEnabled
loginItemSignedIn.text = if (!item.isEnabled) {
root.context.getString(R.string.login_signed_in)
} else diary?.diaryName
with(loginItemCheck) {
if (isEnabled) {
isChecked = !isChecked
checkedList[position] = isChecked
}
keyListener = null
isEnabled = item.isEnabled
isChecked = item.isSelected || !item.isEnabled
}
root.isEnabled = item.isEnabled
root.setOnClickListener {
item.onClick(item)
}
}
}
}
class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) :
RecyclerView.ViewHolder(binding.root)
private class HelpViewHolder(
private val binding: ItemLoginStudentSelectHelpBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LoginStudentSelectItem.Help) {
with(binding) {
loginStudentSelectHelpSymbol.isVisible = item.isSymbolButtonVisible
loginStudentSelectHelpSymbol.setOnClickListener { item.onEnterSymbolClick() }
loginStudentSelectHelpMail.setOnClickListener { item.onContactUsClick() }
loginStudentSelectHelpDiscord.setOnClickListener { item.onDiscordClick() }
}
}
}
private object Differ : ItemCallback<LoginStudentSelectItem>() {
override fun areItemsTheSame(
oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
): Boolean = when {
oldItem is LoginStudentSelectItem.EmptySymbolsHeader && newItem is LoginStudentSelectItem.EmptySymbolsHeader -> true
oldItem is LoginStudentSelectItem.SymbolHeader && newItem is LoginStudentSelectItem.SymbolHeader -> {
oldItem.symbol == newItem.symbol
}
oldItem is LoginStudentSelectItem.Student && newItem is LoginStudentSelectItem.Student -> {
oldItem.student == newItem.student
}
else -> oldItem == newItem
}
override fun areContentsTheSame(
oldItem: LoginStudentSelectItem, newItem: LoginStudentSelectItem
): Boolean = oldItem == newItem
}
}

View File

@ -2,17 +2,16 @@ package io.github.wulkanowy.ui.modules.login.studentselect
import android.os.Bundle
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
@ -36,12 +35,23 @@ class LoginStudentSelectFragment :
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object {
const val ARG_STUDENTS = "STUDENTS"
private lateinit var symbolsNames: Array<String>
private lateinit var symbolsValues: Array<String>
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
override val symbols: Map<String, String> by lazy {
symbolsValues.zip(symbolsNames).toMap()
}
companion object {
private const val ARG_LOGIN = "LOGIN"
private const val ARG_STUDENTS = "STUDENTS"
fun newInstance(loginData: LoginData, registerUser: RegisterUser) =
LoginStudentSelectFragment().apply {
arguments = bundleOf(ARG_STUDENTS to studentsWithSemesters)
arguments = bundleOf(
ARG_LOGIN to loginData,
ARG_STUDENTS to registerUser,
)
}
}
@ -49,34 +59,32 @@ class LoginStudentSelectFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentLoginStudentSelectBinding.bind(view)
symbolsNames = resources.getStringArray(R.array.symbols)
symbolsValues = resources.getStringArray(R.array.symbols_values)
presenter.onAttachView(
view = this,
students = requireArguments().serializable(ARG_STUDENTS),
loginData = requireArguments().serializable(ARG_LOGIN),
registerUser = requireArguments().serializable(ARG_STUDENTS),
)
}
override fun initView() {
(requireActivity() as LoginActivity).showActionBar(true)
loginAdapter.onClickListener = presenter::onItemSelected
with(binding) {
loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() }
loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() }
loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() }
with(loginStudentSelectRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = loginAdapter
}
loginStudentSelectRecycler.adapter = loginAdapter
}
}
override fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>) {
with(loginAdapter) {
items = data
notifyDataSetChanged()
}
override fun updateData(data: List<LoginStudentSelectItem>) {
loginAdapter.submitList(data)
}
override fun navigateToSymbol(loginData: LoginData) {
(requireActivity() as LoginActivity).navigateToSymbolFragment(loginData)
}
override fun navigateToNext() {
@ -84,26 +92,17 @@ class LoginStudentSelectFragment :
}
override fun showProgress(show: Boolean) {
binding.loginStudentSelectProgress.visibility = if (show) VISIBLE else GONE
binding.loginStudentSelectProgress.isVisible = show
}
override fun showContent(show: Boolean) {
binding.loginStudentSelectContent.visibility = if (show) VISIBLE else GONE
binding.loginStudentSelectContent.isVisible = show
}
override fun enableSignIn(enable: Boolean) {
binding.loginStudentSelectSignIn.isEnabled = enable
}
override fun showContact(show: Boolean) {
binding.loginStudentSelectContact.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
}
@ -124,4 +123,9 @@ class LoginStudentSelectFragment :
)
)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,50 @@
package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.data.pojos.RegisterStudent
import io.github.wulkanowy.data.pojos.RegisterSymbol
import io.github.wulkanowy.data.pojos.RegisterUnit
sealed class LoginStudentSelectItem(val type: LoginStudentSelectItemType) {
data class EmptySymbolsHeader(
val isExpanded: Boolean,
val onClick: () -> Unit,
) : LoginStudentSelectItem(LoginStudentSelectItemType.EMPTY_SYMBOLS_HEADER)
data class SymbolHeader(
val symbol: RegisterSymbol,
val humanReadableName: String?,
val isErrorExpanded: Boolean,
val onClick: (RegisterSymbol) -> Unit,
) : LoginStudentSelectItem(LoginStudentSelectItemType.SYMBOL_HEADER)
data class SchoolHeader(
val unit: RegisterUnit,
val isErrorExpanded: Boolean,
val onClick: (RegisterUnit) -> Unit,
) : LoginStudentSelectItem(LoginStudentSelectItemType.SCHOOL_HEADER)
data class Student(
val symbol: RegisterSymbol,
val unit: RegisterUnit,
val student: RegisterStudent,
val isEnabled: Boolean,
val isSelected: Boolean,
val onClick: (Student) -> Unit,
) : LoginStudentSelectItem(LoginStudentSelectItemType.STUDENT)
data class Help(
val onEnterSymbolClick: () -> Unit,
val onContactUsClick: () -> Unit,
val onDiscordClick: () -> Unit,
val isSymbolButtonVisible: Boolean,
) : LoginStudentSelectItem(LoginStudentSelectItemType.HELP)
}
enum class LoginStudentSelectItemType {
EMPTY_SYMBOLS_HEADER,
SYMBOL_HEADER,
SCHOOL_HEADER,
STUDENT,
HELP,
}

View File

@ -1,15 +1,23 @@
package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.logResourceStatus
import io.github.wulkanowy.data.mappers.mapToStudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterStudent
import io.github.wulkanowy.data.pojos.RegisterSymbol
import io.github.wulkanowy.data.pojos.RegisterUnit
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException
import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.ifNullOrBlank
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
@ -19,18 +27,30 @@ class LoginStudentSelectPresenter @Inject constructor(
studentRepository: StudentRepository,
private val loginErrorHandler: LoginErrorHandler,
private val syncManager: SyncManager,
private val analytics: AnalyticsHelper
private val analytics: AnalyticsHelper,
private val appInfo: AppInfo,
) : BasePresenter<LoginStudentSelectView>(loginErrorHandler, studentRepository) {
private var lastError: Throwable? = null
private val selectedStudents = mutableListOf<StudentWithSemesters>()
private lateinit var registerUser: RegisterUser
private lateinit var loginData: LoginData
fun onAttachView(view: LoginStudentSelectView, students: List<StudentWithSemesters>) {
private lateinit var students: List<StudentWithSemesters>
private var isEmptySymbolsExpanded = false
private var expandedSymbolError: RegisterSymbol? = null
private var expandedSchoolError: RegisterUnit? = null
private val selectedStudents = mutableListOf<LoginStudentSelectItem.Student>()
fun onAttachView(
view: LoginStudentSelectView,
loginData: LoginData,
registerUser: RegisterUser,
) {
super.onAttachView(view)
with(view) {
initView()
showContact(false)
enableSignIn(false)
loginErrorHandler.onStudentDuplicate = {
showMessage(it)
@ -38,50 +58,171 @@ class LoginStudentSelectPresenter @Inject constructor(
}
}
if (students.size == 1) registerStudents(students)
loadData(students)
this.loginData = loginData
this.registerUser = registerUser
loadData()
}
private fun loadData() {
resetSelectedState()
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
students = it.dataOrNull.orEmpty()
when (it) {
is Resource.Loading -> Timber.d("Login student select students load started")
is Resource.Success -> refreshItems()
is Resource.Error -> {
errorHandler.dispatch(it.error)
lastError = it.error
refreshItems()
}
}
}.launch()
}
private fun createItems(): List<LoginStudentSelectItem> = buildList {
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
if (emptySymbols.isNotEmpty() && notEmptySymbols.isNotEmpty() && emptySymbols.any { it.symbol == loginData.symbol }) {
add(createEmptySymbolItem(emptySymbols.first { it.symbol == loginData.symbol }))
}
addAll(createNotEmptySymbolItems(notEmptySymbols, students))
addAll(createEmptySymbolItems(emptySymbols, notEmptySymbols.isNotEmpty()))
val helpItem = LoginStudentSelectItem.Help(
onEnterSymbolClick = ::onEnterSymbol,
onContactUsClick = ::onEmailClick,
onDiscordClick = ::onDiscordClick,
isSymbolButtonVisible = "login" !in loginData.baseUrl,
)
add(helpItem)
}
private fun createNotEmptySymbolItems(
notEmptySymbols: List<RegisterSymbol>,
students: List<StudentWithSemesters>,
) = buildList {
notEmptySymbols.forEach { registerSymbol ->
val symbolHeader = LoginStudentSelectItem.SymbolHeader(
symbol = registerSymbol,
humanReadableName = view?.symbols?.get(registerSymbol.symbol),
isErrorExpanded = expandedSymbolError == registerSymbol,
onClick = ::onSymbolItemClick,
)
add(symbolHeader)
registerSymbol.schools.forEach { registerUnit ->
val schoolHeader = LoginStudentSelectItem.SchoolHeader(
unit = registerUnit,
isErrorExpanded = expandedSchoolError == registerUnit,
onClick = ::onUnitItemClick,
)
add(schoolHeader)
registerUnit.students.forEach {
add(createStudentItem(it, registerSymbol, registerUnit, students))
}
}
}
}
private fun createStudentItem(
student: RegisterStudent,
symbol: RegisterSymbol,
school: RegisterUnit,
students: List<StudentWithSemesters>,
) = LoginStudentSelectItem.Student(
symbol = symbol,
unit = school,
student = student,
onClick = ::onItemSelected,
isEnabled = students.none {
it.student.email == registerUser.login
&& it.student.symbol == symbol.symbol
&& it.student.studentId == student.studentId
&& it.student.schoolSymbol == school.schoolId
&& it.student.classId == student.classId
},
isSelected = selectedStudents
.filter { it.symbol.symbol == symbol.symbol }
.filter { it.unit.schoolId == school.schoolId }
.filter { it.student.studentId == student.studentId }
.filter { it.student.classId == student.classId }
.size == 1,
)
private fun createEmptySymbolItems(
emptySymbols: List<RegisterSymbol>,
isNotEmptySymbolsExist: Boolean,
) = buildList {
val filteredEmptySymbols = emptySymbols.filter {
it.error !is AccountPermissionException
}.ifEmpty { emptySymbols.takeIf { !isNotEmptySymbolsExist }.orEmpty() }
if (filteredEmptySymbols.isNotEmpty() && isNotEmptySymbolsExist) {
val emptyHeader = LoginStudentSelectItem.EmptySymbolsHeader(
isExpanded = isEmptySymbolsExpanded,
onClick = ::onEmptySymbolsToggle,
)
add(emptyHeader)
if (isEmptySymbolsExpanded) {
filteredEmptySymbols.forEach {
add(createEmptySymbolItem(it))
}
}
}
if (filteredEmptySymbols.isNotEmpty() && !isNotEmptySymbolsExist) {
filteredEmptySymbols.forEach {
add(createEmptySymbolItem(it))
}
}
}
private fun createEmptySymbolItem(registerSymbol: RegisterSymbol) =
LoginStudentSelectItem.SymbolHeader(
symbol = registerSymbol,
humanReadableName = view?.symbols?.get(registerSymbol.symbol),
isErrorExpanded = expandedSymbolError == registerSymbol,
onClick = ::onSymbolItemClick,
)
fun onSignIn() {
registerStudents(selectedStudents)
}
fun onItemSelected(studentWithSemester: StudentWithSemesters, alreadySaved: Boolean) {
if (alreadySaved) return
private fun onEmptySymbolsToggle() {
isEmptySymbolsExpanded = !isEmptySymbolsExpanded
refreshItems()
}
private fun onItemSelected(item: LoginStudentSelectItem.Student) {
if (!item.isEnabled) return
selectedStudents
.removeAll { it == studentWithSemester }
.let { if (!it) selectedStudents.add(studentWithSemester) }
.removeAll {
it.student.studentId == item.student.studentId &&
it.student.classId == item.student.classId &&
it.unit.schoolId == item.unit.schoolId &&
it.symbol.symbol == item.symbol.symbol
}
.let { if (!it) selectedStudents.add(item) }
view?.enableSignIn(selectedStudents.isNotEmpty())
refreshItems()
}
private fun compareStudents(a: Student, b: Student): Boolean {
return a.email == b.email
&& a.symbol == b.symbol
&& a.studentId == b.studentId
&& a.schoolSymbol == b.schoolSymbol
&& a.classId == b.classId
private fun onSymbolItemClick(symbol: RegisterSymbol) {
expandedSymbolError = if (symbol != expandedSymbolError) symbol else null
refreshItems()
}
private fun loadData(studentsWithSemesters: List<StudentWithSemesters>) {
resetSelectedState()
resourceFlow { studentRepository.getSavedStudents(false) }.onEach {
when (it) {
is Resource.Loading -> Timber.d("Login student select students load started")
is Resource.Success -> view?.updateData(studentsWithSemesters.map { studentWithSemesters ->
studentWithSemesters to it.data.any { item ->
compareStudents(studentWithSemesters.student, item.student)
}
})
is Resource.Error -> {
errorHandler.dispatch(it.error)
lastError = it.error
view?.updateData(studentsWithSemesters.map { student -> student to false })
}
}
}.launch()
private fun onUnitItemClick(unit: RegisterUnit) {
expandedSchoolError = if (unit != expandedSchoolError) unit else null
refreshItems()
}
private fun resetSelectedState() {
@ -89,7 +230,20 @@ class LoginStudentSelectPresenter @Inject constructor(
view?.enableSignIn(false)
}
private fun registerStudents(studentsWithSemesters: List<StudentWithSemesters>) {
private fun refreshItems() {
view?.updateData(createItems())
}
private fun registerStudents(students: List<LoginStudentSelectItem>) {
val studentsWithSemesters = students
.filterIsInstance<LoginStudentSelectItem.Student>().map { item ->
item.student.mapToStudentWithSemesters(
user = registerUser,
symbol = item.symbol,
unit = item.unit,
colors = appInfo.defaultColorsForAvatar,
)
}
resourceFlow { studentRepository.saveStudents(studentsWithSemesters) }
.logResourceStatus("registration")
.onEach {
@ -107,7 +261,6 @@ class LoginStudentSelectPresenter @Inject constructor(
view?.apply {
showProgress(false)
showContent(true)
showContact(true)
}
lastError = it.error
loginErrorHandler.dispatch(it.error)
@ -117,12 +270,22 @@ class LoginStudentSelectPresenter @Inject constructor(
}.launch("register")
}
fun onDiscordClick() {
private fun onEnterSymbol() {
view?.navigateToSymbol(loginData)
}
private fun onDiscordClick() {
view?.openDiscordInvite()
}
fun onEmailClick() {
view?.openEmail(lastError?.message.ifNullOrBlank { "empty" })
private fun onEmailClick() {
view?.openEmail(lastError?.message.ifNullOrBlank {
registerUser.symbols.flatMap { symbol ->
symbol.schools.map { it.error?.message } + symbol.error?.message
}.filterNotNull().distinct().joinToString("; ") {
it.take(46) + "..."
}.ifEmpty { "blank" }
})
}
private fun logRegisterEvent(

View File

@ -1,13 +1,17 @@
package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
interface LoginStudentSelectView : BaseView {
val symbols: Map<String, String>
fun initView()
fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>)
fun updateData(data: List<LoginStudentSelectItem>)
fun navigateToSymbol(loginData: LoginData)
fun navigateToNext()
@ -17,8 +21,6 @@ interface LoginStudentSelectView : BaseView {
fun enableSignIn(enable: Boolean)
fun showContact(show: Boolean)
fun openDiscordInvite()
fun openEmail(lastError: String)

View File

@ -12,7 +12,7 @@ import androidx.core.text.parseAsHtml
import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
import io.github.wulkanowy.ui.base.BaseFragment
@ -42,6 +42,8 @@ class LoginSymbolFragment :
}
}
override val symbolValue: String? get() = binding.loginSymbolName.text?.toString()
override val symbolNameError: CharSequence?
get() = binding.loginSymbolNameLayout.error
@ -58,7 +60,7 @@ class LoginSymbolFragment :
(requireActivity() as LoginActivity).showActionBar(true)
with(binding) {
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin() }
loginSymbolFaq.setOnClickListener { presenter.onFaqClick() }
loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() }
@ -92,9 +94,13 @@ class LoginSymbolFragment :
}
override fun setErrorSymbolRequire() {
binding.loginSymbolNameLayout.apply {
setErrorSymbol(getString(R.string.error_field_required))
}
override fun setErrorSymbol(message: String) {
with(binding.loginSymbolNameLayout) {
requestFocus()
error = getString(R.string.error_field_required)
error = message
}
}
@ -125,8 +131,8 @@ class LoginSymbolFragment :
binding.loginSymbolContainer.visibility = if (show) VISIBLE else GONE
}
override fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>) {
(activity as? LoginActivity)?.navigateToStudentSelect(studentsWithSemesters)
override fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser) {
(activity as? LoginActivity)?.navigateToStudentSelect(loginData, registerUser)
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -1,9 +1,12 @@
package io.github.wulkanowy.ui.modules.login.symbol
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.onResourceNotLoading
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.sdk.scrapper.getNormalizedSymbol
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
@ -23,9 +26,14 @@ class LoginSymbolPresenter @Inject constructor(
lateinit var loginData: LoginData
private var registerUser: RegisterUser? = null
fun onAttachView(view: LoginSymbolView, loginData: LoginData) {
super.onAttachView(view)
this.loginData = loginData
loginErrorHandler.onBadCredentials = {
view.setErrorSymbol(it.orEmpty())
}
with(view) {
initView()
showContact(false)
@ -39,20 +47,24 @@ class LoginSymbolPresenter @Inject constructor(
view?.apply { if (symbolNameError != null) clearSymbolError() }
}
fun attemptLogin(symbol: String) {
if (symbol.isBlank()) {
fun attemptLogin() {
if (view?.symbolValue.isNullOrBlank()) {
view?.setErrorSymbolRequire()
return
}
loginData = loginData.copy(
symbol = view?.symbolValue?.getNormalizedSymbol(),
)
resourceFlow {
studentRepository.getStudentsScrapper(
studentRepository.getUserSubjectsFromScrapper(
email = loginData.login,
password = loginData.password,
scrapperBaseUrl = loginData.baseUrl,
symbol = symbol,
symbol = view?.symbolValue.orEmpty(),
)
}.onEach {
registerUser = it.dataOrNull
when (it) {
is Resource.Loading -> view?.run {
Timber.i("Login with symbol started")
@ -61,7 +73,7 @@ class LoginSymbolPresenter @Inject constructor(
showContent(false)
}
is Resource.Success -> {
when (it.data.size) {
when (it.data.symbols.size) {
0 -> {
Timber.i("Login with symbol result: Empty student list")
view?.run {
@ -71,15 +83,14 @@ class LoginSymbolPresenter @Inject constructor(
}
else -> {
Timber.i("Login with symbol result: Success")
view?.navigateToStudentSelect(requireNotNull(it.data))
view?.navigateToStudentSelect(loginData, requireNotNull(it.data))
}
}
analytics.logEvent(
"registration_symbol",
"success" to true,
"students" to it.data.size,
"scrapperBaseUrl" to loginData.baseUrl,
"symbol" to symbol,
"symbol" to view?.symbolValue,
"error" to "No error"
)
}
@ -90,7 +101,7 @@ class LoginSymbolPresenter @Inject constructor(
"success" to false,
"students" to -1,
"scrapperBaseUrl" to loginData.baseUrl,
"symbol" to symbol,
"symbol" to view?.symbolValue,
"error" to it.error.message.ifNullOrBlank { "No message" }
)
loginErrorHandler.dispatch(it.error)
@ -111,6 +122,12 @@ class LoginSymbolPresenter @Inject constructor(
}
fun onEmailClick() {
view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank { "empty" })
view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank {
registerUser?.symbols?.flatMap { symbol ->
symbol.schools.map { it.error?.message } + symbol.error?.message
}?.filterNotNull()?.distinct()?.joinToString(";") {
it.take(46) + "..."
} ?: "blank"
})
}
}

View File

@ -1,10 +1,13 @@
package io.github.wulkanowy.ui.modules.login.symbol
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.login.LoginData
interface LoginSymbolView : BaseView {
val symbolValue: String?
val symbolNameError: CharSequence?
fun initView()
@ -15,6 +18,8 @@ interface LoginSymbolView : BaseView {
fun setErrorSymbolRequire()
fun setErrorSymbol(message: String)
fun clearSymbolError()
fun clearAndFocusSymbol()
@ -27,7 +32,7 @@ interface LoginSymbolView : BaseView {
fun showContent(show: Boolean)
fun navigateToStudentSelect(studentsWithSemesters: List<StudentWithSemesters>)
fun navigateToStudentSelect(loginData: LoginData, registerUser: RegisterUser)
fun showContact(show: Boolean)

View File

@ -3,92 +3,14 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/loginStudentSelectProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
android:orientation="vertical"
tools:context=".ui.modules.login.studentselect.LoginStudentSelectFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/loginStudentSelectContent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/loginStudentSelectContact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<View
android:id="@+id/loginStudentSelectContactTopDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/loginStudentSelectContactHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:layout_marginBottom="16dp"
android:gravity="center_horizontal"
android:text="@string/login_contact_header"
android:textSize="14sp"
app:fontFamily="sans-serif-medium" />
<LinearLayout
android:id="@+id/loginStudentSelectContactButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/loginStudentSelectContactEmail"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/login_contact_email"
app:icon="@drawable/ic_more_messages" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginStudentSelectContactDiscord"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:text="@string/login_contact_discord"
app:icon="@drawable/ic_about_discord" />
</LinearLayout>
<View
android:id="@+id/loginStudentSelectContactBottomDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
</LinearLayout>
<TextView
android:id="@+id/loginStudentSelectHeader"
android:layout_width="match_parent"
@ -106,22 +28,24 @@
app:layout_constraintBottom_toTopOf="@id/loginStudentSelectRecycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginStudentSelectContact"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/loginStudentSelectRecycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="144dp"
android:fadingEdge="vertical"
android:fadingEdgeLength="100dp"
android:requiresFadingEdge="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="@id/loginStudentSelectSignIn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="432dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginStudentSelectHeader"
tools:itemCount="6"
tools:listitem="@layout/item_login_student_select" />
tools:itemCount="33"
tools:listitem="@layout/item_login_student_select_student" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginStudentSelectSignIn"
@ -136,4 +60,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginStudentSelectRecycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/loginStudentSelectProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="?selectableItemBackground"
android:paddingHorizontal="16dp">
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/login_student_select_empty_symbol_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="16dp"
android:text="@string/login_other_search_locations"
android:textColor="?android:textColorPrimary"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/login_student_select_empty_symbol_chevron"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="16dp"
android:rotation="90"
android:src="@drawable/ic_chevron_right"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?android:textColorPrimary"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="24dp"
android:paddingVertical="8dp">
<TextView
android:id="@+id/login_student_select_header_school_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:textColorPrimary"
app:layout_constraintTop_toTopOf="parent"
tools:text="Publiczna szkoła Wulkanowego" />
<TextView
android:id="@+id/login_student_select_header_school_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/login_no_active_student"
android:textColor="?colorTimetableChange"
app:layout_constraintTop_toBottomOf="@id/login_student_select_header_school_name" />
<TextView
android:id="@+id/login_student_select_header_school_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?colorError"
app:layout_constraintTop_toBottomOf="@id/login_student_select_header_school_details"
tools:text="@tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
<TextView
android:id="@+id/login_student_select_header_symbol_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:textColorPrimary"
app:layout_constraintTop_toTopOf="parent"
tools:text="powiatjaroslawski" />
<TextView
android:id="@+id/login_student_select_header_symbol_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="?android:textColorSecondary"
app:layout_constraintTop_toBottomOf="@id/login_student_select_header_symbol_value"
tools:text="Jan Kowalski" />
<TextView
android:id="@+id/login_student_select_header_symbol_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?colorError"
app:layout_constraintTop_toBottomOf="@id/login_student_select_header_symbol_username"
tools:text="@tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/login_student_select_help_symbol"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/login_symbol_enter"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/login_student_select_help_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/login_contact_header"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/login_student_select_help_mail"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_student_select_help_symbol" />
<com.google.android.material.button.MaterialButton
android:id="@+id/login_student_select_help_mail"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/login_contact_email"
app:icon="@drawable/ic_more_messages"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintEnd_toEndOf="@id/login_student_select_help_title"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/login_student_select_help_title"
app:layout_constraintTop_toBottomOf="@id/login_student_select_help_symbol" />
<com.google.android.material.button.MaterialButton
android:id="@+id/login_student_select_help_discord"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/login_contact_discord"
app:icon="@drawable/ic_about_discord"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/login_student_select_help_mail"
app:layout_constraintTop_toTopOf="@id/login_student_select_help_mail" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:minHeight="72dp"
android:minHeight="56dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
tools:context=".ui.modules.login.studentselect.LoginStudentSelectAdapter">
@ -14,9 +14,10 @@
android:layout_width="32dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="28dp"
android:background="@android:color/transparent"
android:clickable="false"
tools:text=" " />
<TextView
@ -32,34 +33,18 @@
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/loginItemSchool"
android:id="@+id/loginItemSignedIn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/loginItemName"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/loginItemCheck"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="2"
android:textColor="?android:textColorSecondary"
android:textSize="14sp"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/loginItemSignedIn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="20dp"
android:layout_below="@id/loginItemSchool"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/loginItemCheck"
android:ellipsize="end"
android:enabled="false"
android:gravity="bottom"
android:maxLines="1"
android:minHeight="20dp"
android:text="@string/login_signed_in"
android:textColor="?android:textColorSecondary"
android:textSize="14sp"
android:visibility="gone"
tools:visibility="visible" />
android:textSize="14sp" />
</RelativeLayout>

View File

@ -55,7 +55,7 @@
<string name="login_invalid_symbol">Invalid symbol</string>
<string name="login_incorrect_symbol">Student not found. Validate the symbol and the chosen variation of the UONET+ register</string>
<string name="login_duplicate_student">Selected student is already logged in</string>
<string name="login_symbol_helper">The symbol can be found on the register page in&#160;<b>Uczeń</b> →&#160;<b>Dostęp Mobilny</b>&#160;<b>Zarejestruj urządzenie mobilne</b>.\n\nMake sure that you have set the appropriate register variant in the <b>UONET+ register variant</b> field on the previous screen</string>
<string name="login_symbol_helper">The symbol can be found on the register page in&#160;<b>Uczeń</b> →&#160;<b>Dostęp Mobilny</b>&#160;<b>Wygeneruj kod dostępu</b>.\n\nMake sure that you have set the appropriate register variant in the <b>UONET+ register variant</b> field on the first login screen</string>
<string name="login_select_student">Select students to log in to the application</string>
<string name="login_advanced">Other options</string>
<string name="login_advanced_warning_mobile_api">In this mode, a lucky number does not work, a class grade stats, summary of attendance, excuse for absence, completed lessons, school information and preview of the list of registered devices</string>
@ -74,6 +74,9 @@
<string name="login_recover">Recover</string>
<string name="login_signed_in">Student is already signed in</string>
<string name="login_host_standard">Standard</string>
<string name="login_other_search_locations">Other search locations</string>
<string name="login_no_active_student">No active students found</string>
<string name="login_symbol_enter">Enter a different symbol</string>
<!--Notifications-->
<string name="notifications_header_title">Enable notifications</string>
<string name="notifications_header_description">Enable notifications so you don\'t miss message from teacher or new grade</string>

View File

@ -1,9 +1,9 @@
package io.github.wulkanowy.ui.modules.login.form
import io.github.wulkanowy.MainCoroutineRule
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.sdk.scrapper.Scrapper
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.mockk.*
@ -12,7 +12,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.IOException
import java.time.Instant
class LoginFormPresenterTest {
@ -33,6 +32,15 @@ class LoginFormPresenterTest {
private lateinit var presenter: LoginFormPresenter
private val registerUser = RegisterUser(
email = "",
password = "",
login = "",
baseUrl = "",
loginType = Scrapper.LoginType.AUTO,
symbols = listOf(),
)
@Before
fun setUp() {
MockKAnnotations.init(this)
@ -104,32 +112,9 @@ class LoginFormPresenterTest {
@Test
fun loginTest() {
val studentTest = Student(
email = "test@",
password = "123",
scrapperBaseUrl = "https://fakelog.cf/?email",
loginType = "AUTO",
studentName = "",
schoolSymbol = "",
schoolName = "",
studentId = 0,
classId = 1,
isCurrent = false,
symbol = "",
registrationDate = Instant.now(),
className = "",
mobileBaseUrl = "",
privateKey = "",
certificateKey = "",
loginMode = "",
userLoginId = 0,
schoolShortName = "",
isParent = false,
userName = ""
)
coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf(
StudentWithSemesters(studentTest, emptyList())
)
coEvery {
repository.getUserSubjectsFromScrapper(any(), any(), any(), any())
} returns registerUser
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
@ -146,7 +131,9 @@ class LoginFormPresenterTest {
@Test
fun loginEmptyTest() {
coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf()
coEvery {
repository.getUserSubjectsFromScrapper(any(), any(), any(), any())
} returns registerUser
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
every { loginFormView.formHostValue } returns "https://fakelog.cf/?email"
@ -162,7 +149,9 @@ class LoginFormPresenterTest {
@Test
fun loginEmptyTwiceTest() {
coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } returns listOf()
coEvery {
repository.getUserSubjectsFromScrapper(any(), any(), any(), any())
} returns registerUser
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
every { loginFormView.formHostValue } returns "https://fakelog.cf/?email"
@ -180,7 +169,14 @@ class LoginFormPresenterTest {
@Test
fun loginErrorTest() {
val testException = IOException("test")
coEvery { repository.getStudentsScrapper(any(), any(), any(), any()) } throws testException
coEvery {
repository.getUserSubjectsFromScrapper(
any(),
any(),
any(),
any()
)
} throws testException
every { loginFormView.formUsernameValue } returns "@"
every { loginFormView.formPassValue } returns "123456"
every { loginFormView.formHostValue } returns "https://fakelog.cf/?email"

View File

@ -1,18 +1,22 @@
package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.MainCoroutineRule
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.pojos.RegisterStudent
import io.github.wulkanowy.data.pojos.RegisterSymbol
import io.github.wulkanowy.data.pojos.RegisterUnit
import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.sdk.scrapper.Scrapper
import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.mockk.*
import io.mockk.impl.annotations.MockK
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.time.Instant
class LoginStudentSelectPresenterTest {
@ -22,7 +26,7 @@ class LoginStudentSelectPresenterTest {
@MockK(relaxed = true)
lateinit var errorHandler: LoginErrorHandler
@MockK(relaxed = true)
@MockK
lateinit var loginStudentSelectView: LoginStudentSelectView
@MockK
@ -34,33 +38,55 @@ class LoginStudentSelectPresenterTest {
@MockK(relaxed = true)
lateinit var syncManager: SyncManager
private val appInfo = AppInfo()
private lateinit var presenter: LoginStudentSelectPresenter
private val testStudent by lazy {
Student(
email = "test",
password = "test123",
scrapperBaseUrl = "https://fakelog.cf",
loginType = "AUTO",
symbol = "",
isCurrent = false,
studentId = 0,
schoolName = "",
schoolSymbol = "",
classId = 1,
studentName = "",
registrationDate = Instant.now(),
className = "",
loginMode = "",
certificateKey = "",
privateKey = "",
mobileBaseUrl = "",
schoolShortName = "",
userLoginId = 1,
isParent = false,
userName = ""
)
}
private val loginData = LoginData(
login = "",
password = "",
baseUrl = "",
symbol = null,
)
private val subject = RegisterStudent(
studentId = 0,
studentName = "",
studentSecondName = "",
studentSurname = "",
className = "",
classId = 0,
isParent = false,
semesters = listOf(),
)
private val school = RegisterUnit(
userLoginId = 0,
schoolId = "",
schoolName = "",
schoolShortName = "",
parentIds = listOf(),
studentIds = listOf(),
employeeIds = listOf(),
error = null,
students = listOf(subject)
)
private val symbol = RegisterSymbol(
symbol = "",
error = null,
userName = "",
schools = listOf(school),
)
private val registerUser = RegisterUser(
email = "",
password = "",
login = "",
baseUrl = "",
loginType = Scrapper.LoginType.AUTO,
symbols = listOf(symbol),
)
private val testException by lazy { RuntimeException("Problem") }
@ -69,30 +95,44 @@ class LoginStudentSelectPresenterTest {
MockKAnnotations.init(this)
clearMocks(studentRepository, loginStudentSelectView)
coEvery { studentRepository.getSavedStudents(false) } returns emptyList()
every { loginStudentSelectView.initView() } just Runs
every { loginStudentSelectView.showContact(any()) } just Runs
every { loginStudentSelectView.symbols } returns emptyMap()
every { loginStudentSelectView.enableSignIn(any()) } just Runs
every { loginStudentSelectView.showProgress(any()) } just Runs
every { loginStudentSelectView.showContent(any()) } just Runs
presenter = LoginStudentSelectPresenter(studentRepository, errorHandler, syncManager, analytics)
presenter.onAttachView(loginStudentSelectView, emptyList())
presenter = LoginStudentSelectPresenter(
studentRepository = studentRepository,
loginErrorHandler = errorHandler,
syncManager = syncManager,
analytics = analytics,
appInfo = appInfo,
)
}
@Test
fun initViewTest() {
presenter.onAttachView(loginStudentSelectView, loginData, registerUser)
verify { loginStudentSelectView.initView() }
}
@Test
fun onSelectedStudentTest() {
coEvery {
studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList())))
} just Runs
val itemsSlot = slot<List<LoginStudentSelectItem>>()
every { loginStudentSelectView.updateData(capture(itemsSlot)) } just Runs
presenter.onAttachView(loginStudentSelectView, loginData, registerUser)
coEvery { studentRepository.saveStudents(any()) } just Runs
every { loginStudentSelectView.navigateToNext() } just Runs
presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
itemsSlot.captured.filterIsInstance<LoginStudentSelectItem.Student>().first().let {
it.onClick(it)
}
presenter.onSignIn()
verify { loginStudentSelectView.showContent(false) }
@ -102,13 +142,15 @@ class LoginStudentSelectPresenterTest {
@Test
fun onSelectedStudentErrorTest() {
coEvery {
studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList())))
} throws testException
val itemsSlot = slot<List<LoginStudentSelectItem>>()
every { loginStudentSelectView.updateData(capture(itemsSlot)) } just Runs
presenter.onAttachView(loginStudentSelectView, loginData, registerUser)
coEvery { studentRepository.logoutStudent(testStudent) } just Runs
coEvery { studentRepository.saveStudents(any()) } throws testException
presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
itemsSlot.captured.filterIsInstance<LoginStudentSelectItem.Student>().first().let {
it.onClick(it)
}
presenter.onSignIn()
verify { loginStudentSelectView.showContent(false) }