[Login/Podlasie] Add option to logout other devices.

This commit is contained in:
Kuba Szczodrzyński 2020-10-16 23:50:44 +02:00
parent ecdaaeae65
commit 6ecb97b87e
7 changed files with 214 additions and 118 deletions

View File

@ -119,6 +119,7 @@ const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"
const val PODLASIE_API_VERSION = "1.0.31"
const val PODLASIE_API_VERSION = "1.0.62"
const val PODLASIE_API_URL = "https://cpdklaser.zeto.bialystok.pl/api"
const val PODLASIE_API_USER_ENDPOINT = "/pobierzDaneUcznia"
const val PODLASIE_API_LOGOUT_DEVICES_ENDPOINT = "/wyczyscUrzadzenia"

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE
import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_LOGOUT_DEVICES_ENDPOINT
import pl.szczodrzynski.edziennik.data.api.PODLASIE_API_USER_ENDPOINT
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieApi
@ -22,50 +23,62 @@ class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) {
private val api = PodlasieApi(data, null)
init {
PodlasieLoginApi(data) {
doLogin()
}
}
private fun doLogin() {
val loginStoreId = data.loginStore.id
val loginStoreType = LOGIN_TYPE_PODLASIE
PodlasieLoginApi(data) {
api.apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
val uuid = json.getString("Uuid")
val login = json.getString("Login")
val firstName = json.getString("FirstName")
val lastName = json.getString("LastName")
val studentNameLong = "$firstName $lastName".fixName()
val studentNameShort = studentNameLong.getShortName()
val schoolName = json.getString("SchoolName")
val className = json.getString("SchoolClass")
val schoolYear = json.getString("ActualSchoolYear")?.replace(' ', '/')
val semester = json.getString("ActualTermShortcut")?.length
val apiUrl = json.getString("URL")
val profile = Profile(
loginStoreId,
loginStoreId,
loginStoreType,
studentNameLong,
login,
studentNameLong,
studentNameShort,
null
).apply {
studentData["studentId"] = uuid
studentData["studentLogin"] = login
studentData["schoolName"] = schoolName
studentData["className"] = className
studentData["schoolYear"] = schoolYear
studentData["currentSemester"] = semester ?: 1
studentData["apiUrl"] = apiUrl
schoolYear?.split('/')?.get(0)?.toInt()?.let {
studentSchoolYearStart = it
}
studentClassName = className
}
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(profile), data.loginStore))
onSuccess()
if (data.loginStore.getLoginData("logoutDevices", false)) {
data.loginStore.removeLoginData("logoutDevices")
api.apiGet(TAG, PODLASIE_API_LOGOUT_DEVICES_ENDPOINT) {
doLogin()
}
return
}
api.apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
val uuid = json.getString("Uuid")
val login = json.getString("Login")
val firstName = json.getString("FirstName")
val lastName = json.getString("LastName")
val studentNameLong = "$firstName $lastName".fixName()
val studentNameShort = studentNameLong.getShortName()
val schoolName = json.getString("SchoolName")
val className = json.getString("SchoolClass")
val schoolYear = json.getString("ActualSchoolYear")?.replace(' ', '/')
val semester = json.getString("ActualTermShortcut")?.length
val apiUrl = json.getString("URL")
val profile = Profile(
loginStoreId,
loginStoreId,
loginStoreType,
studentNameLong,
login,
studentNameLong,
studentNameShort,
null
).apply {
studentData["studentId"] = uuid
studentData["studentLogin"] = login
studentData["schoolName"] = schoolName
studentData["className"] = className
studentData["schoolYear"] = schoolYear
studentData["currentSemester"] = semester ?: 1
studentData["apiUrl"] = apiUrl
schoolYear?.split('/')?.get(0)?.toInt()?.let {
studentSchoolYearStart = it
}
studentClassName = className
}
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(profile), data.loginStore))
onSuccess()
}
}
}

View File

@ -22,8 +22,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.databinding.LoginFormCheckboxItemBinding
import pl.szczodrzynski.edziennik.databinding.LoginFormFieldItemBinding
import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding
import pl.szczodrzynski.edziennik.databinding.LoginFormItemBinding
import pl.szczodrzynski.navlib.colorAttr
import java.util.*
import kotlin.coroutines.CoroutineContext
@ -75,33 +76,48 @@ class LoginFormFragment : Fragment(), CoroutineScope {
b.subTitle.text = platformName ?: app.getString(mode.name)
b.text.text = platformGuideText ?: app.getString(mode.guideText)
val credentials = mutableMapOf<LoginInfo.Credential, LoginFormItemBinding>()
val credentials = mutableMapOf<LoginInfo.BaseCredential, Any>()
for (credential in mode.credentials) {
if (platformFormFields?.contains(credential.keyName) == false)
continue
val b = LoginFormItemBinding.inflate(layoutInflater)
b.textLayout.hint = app.getString(credential.name)
if (credential.hideText) {
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
if (credential is LoginInfo.FormField) {
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
b.textLayout.hint = app.getString(credential.name)
if (credential.hideText) {
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
}
b.textEdit.addTextChangedListener {
b.textLayout.error = null
}
b.textEdit.id = credential.name
b.textEdit.setText(arguments?.getString(credential.keyName) ?: "")
b.textLayout.startIconDrawable = IconicsDrawable(activity)
.icon(credential.icon)
.sizeDp(24)
.paddingDp(2)
.colorAttr(activity, R.attr.colorOnBackground)
this.b.formContainer.addView(b.root)
credentials[credential] = b
}
b.textEdit.addTextChangedListener {
b.textLayout.error = null
if (credential is LoginInfo.FormCheckbox) {
val b = LoginFormCheckboxItemBinding.inflate(layoutInflater)
b.checkbox.text = app.getString(credential.name)
b.checkbox.onChange { _, _ ->
b.errorText.text = null
}
if (arguments?.containsKey(credential.keyName) == true) {
b.checkbox.isChecked = arguments?.getBoolean(credential.keyName) == true
}
this.b.formContainer.addView(b.root)
credentials[credential] = b
}
b.textEdit.id = credential.name
b.textEdit.setText(arguments?.getString(credential.keyName) ?: "")
b.textLayout.startIconDrawable = IconicsDrawable(activity)
.icon(credential.icon)
.sizeDp(24)
.paddingDp(2)
.colorAttr(activity, R.attr.colorOnBackground)
this.b.formContainer.addView(b.root)
credentials[credential] = b
}
activity.lastError?.let { error ->
@ -109,7 +125,12 @@ class LoginFormFragment : Fragment(), CoroutineScope {
startCoroutineTimer(delayMillis = 200L) {
for (credential in credentials) {
credential.key.errorCodes[error.errorCode]?.let {
credential.value.textLayout.error = app.getString(it)
(credential.value as? LoginFormFieldItemBinding)?.let { b ->
b.textLayout.error = app.getString(it)
}
(credential.value as? LoginFormCheckboxItemBinding)?.let { b ->
b.errorText.text = app.getString(it)
}
return@startCoroutineTimer
}
}
@ -137,35 +158,42 @@ class LoginFormFragment : Fragment(), CoroutineScope {
var hasErrors = false
credentials.forEach { (credential, b) ->
var text = b.textEdit.text?.toString() ?: return@forEach
if (!credential.hideText)
text = text.trim()
if (credential is LoginInfo.FormField && b is LoginFormFieldItemBinding) {
var text = b.textEdit.text?.toString() ?: return@forEach
if (!credential.hideText)
text = text.trim()
if (credential.caseMode == LoginInfo.Credential.CaseMode.UPPER_CASE)
text = text.toUpperCase(Locale.getDefault())
if (credential.caseMode == LoginInfo.Credential.CaseMode.LOWER_CASE)
text = text.toLowerCase(Locale.getDefault())
if (credential.caseMode == LoginInfo.FormField.CaseMode.UPPER_CASE)
text = text.toUpperCase(Locale.getDefault())
if (credential.caseMode == LoginInfo.FormField.CaseMode.LOWER_CASE)
text = text.toLowerCase(Locale.getDefault())
credential.stripTextRegex?.let {
text = text.replace(it.toRegex(), "")
credential.stripTextRegex?.let {
text = text.replace(it.toRegex(), "")
}
b.textEdit.setText(text)
if (credential.isRequired && text.isBlank()) {
b.textLayout.error = app.getString(credential.emptyText)
hasErrors = true
return@forEach
}
if (!text.matches(credential.validationRegex.toRegex())) {
b.textLayout.error = app.getString(credential.invalidText)
hasErrors = true
return@forEach
}
payload.putString(credential.keyName, text)
arguments?.putString(credential.keyName, text)
}
b.textEdit.setText(text)
if (credential.isRequired && text.isBlank()) {
b.textLayout.error = app.getString(credential.emptyText)
hasErrors = true
return@forEach
if (credential is LoginInfo.FormCheckbox && b is LoginFormCheckboxItemBinding) {
val checked = b.checkbox.isChecked
payload.putBoolean(credential.keyName, checked)
arguments?.putBoolean(credential.keyName, checked)
}
if (!text.matches(credential.validationRegex.toRegex())) {
b.textLayout.error = app.getString(credential.invalidText)
hasErrors = true
return@forEach
}
payload.putString(credential.keyName, text)
arguments?.putString(credential.keyName, text)
}
if (hasErrors)

View File

@ -15,7 +15,7 @@ import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
object LoginInfo {
private fun getEmailCredential(keyName: String) = Credential(
private fun getEmailCredential(keyName: String) = FormField(
keyName = keyName,
name = R.string.login_hint_email,
icon = CommunityMaterial.Icon.cmd_at,
@ -24,9 +24,9 @@ object LoginInfo {
errorCodes = mapOf(),
isRequired = true,
validationRegex = "([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
)
private fun getPasswordCredential(keyName: String) = Credential(
private fun getPasswordCredential(keyName: String) = FormField(
keyName = keyName,
name = R.string.login_hint_password,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
@ -94,7 +94,7 @@ object LoginInfo {
hintText = R.string.login_mode_librus_jst_hint,
guideText = R.string.login_mode_librus_jst_guide,
credentials = listOf(
Credential(
FormField(
keyName = "accountCode",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon.cmd_code_braces,
@ -103,9 +103,9 @@ object LoginInfo {
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[A-Z0-9_]+",
caseMode = Credential.CaseMode.UPPER_CASE
caseMode = FormField.CaseMode.UPPER_CASE
),
Credential(
FormField(
keyName = "accountPin",
name = R.string.login_hint_pin,
icon = CommunityMaterial.Icon2.cmd_lock,
@ -114,7 +114,7 @@ object LoginInfo {
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[a-z0-9_]+",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
@ -138,7 +138,7 @@ object LoginInfo {
guideText = R.string.login_mode_vulcan_api_guide,
isRecommended = true,
credentials = listOf(
Credential(
FormField(
keyName = "deviceToken",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon.cmd_code_braces,
@ -149,9 +149,9 @@ object LoginInfo {
),
isRequired = true,
validationRegex = "[A-Z0-9]{5,12}",
caseMode = Credential.CaseMode.UPPER_CASE
caseMode = FormField.CaseMode.UPPER_CASE
),
Credential(
FormField(
keyName = "symbol",
name = R.string.login_hint_symbol,
icon = CommunityMaterial.Icon2.cmd_school,
@ -162,9 +162,9 @@ object LoginInfo {
),
isRequired = true,
validationRegex = "[a-z0-9_-]+",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
),
Credential(
FormField(
keyName = "devicePin",
name = R.string.login_hint_pin,
icon = CommunityMaterial.Icon2.cmd_lock,
@ -175,7 +175,7 @@ object LoginInfo {
),
isRequired = true,
validationRegex = "[0-9]+",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
@ -222,7 +222,7 @@ object LoginInfo {
hintText = R.string.login_mode_mobidziennik_web_hint,
guideText = R.string.login_mode_mobidziennik_web_guide,
credentials = listOf(
Credential(
FormField(
keyName = "username",
name = R.string.login_hint_login_email,
icon = CommunityMaterial.Icon.cmd_account_outline,
@ -231,9 +231,9 @@ object LoginInfo {
errorCodes = mapOf(),
isRequired = true,
validationRegex = "^[a-z0-9_\\-@+.]+$",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
),
Credential(
FormField(
keyName = "password",
name = R.string.login_hint_password,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
@ -246,7 +246,7 @@ object LoginInfo {
validationRegex = ".*",
hideText = true
),
Credential(
FormField(
keyName = "serverName",
name = R.string.login_hint_address,
icon = CommunityMaterial.Icon2.cmd_web,
@ -257,7 +257,7 @@ object LoginInfo {
),
isRequired = true,
validationRegex = "^[a-z0-9_\\-]+\$",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
@ -280,7 +280,7 @@ object LoginInfo {
hintText = R.string.login_mode_idziennik_web_hint,
guideText = R.string.login_mode_idziennik_web_guide,
credentials = listOf(
Credential(
FormField(
keyName = "schoolName",
name = R.string.login_hint_school_name,
icon = CommunityMaterial.Icon2.cmd_school,
@ -291,9 +291,9 @@ object LoginInfo {
),
isRequired = true,
validationRegex = "^[a-z0-9_\\-.]+$",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
),
Credential(
FormField(
keyName = "username",
name = R.string.login_hint_username,
icon = CommunityMaterial.Icon.cmd_account_outline,
@ -302,7 +302,7 @@ object LoginInfo {
errorCodes = mapOf(),
isRequired = true,
validationRegex = "^[a-z0-9_\\-.]+$",
caseMode = Credential.CaseMode.LOWER_CASE
caseMode = FormField.CaseMode.LOWER_CASE
),
getPasswordCredential("password")
),
@ -346,7 +346,7 @@ object LoginInfo {
icon = R.drawable.login_mode_podlasie_api,
guideText = R.string.login_mode_podlasie_api_guide,
credentials = listOf(
Credential(
FormField(
keyName = "apiToken",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
@ -355,7 +355,15 @@ object LoginInfo {
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[a-zA-Z0-9]{10}",
caseMode = Credential.CaseMode.UNCHANGED
caseMode = FormField.CaseMode.UNCHANGED
),
FormCheckbox(
keyName = "logoutDevices",
name = R.string.login_podlasie_logout_devices,
checked = false,
errorCodes = mapOf(
ERROR_LOGIN_PODLASIE_API_DEVICE_LIMIT to R.string.error_602_reason
)
)
),
errorCodes = mapOf()
@ -392,7 +400,7 @@ object LoginInfo {
val isTesting: Boolean = false,
val isPlatformSelection: Boolean = false,
val credentials: List<Credential>,
val credentials: List<BaseCredential>,
val errorCodes: Map<Int, Int>
)
@ -409,11 +417,18 @@ object LoginInfo {
val apiData: JsonObject
)
data class Credential(
val keyName: String,
open class BaseCredential(
open val keyName: String,
@StringRes
open val name: Int,
open val errorCodes: Map<Int, Int>
)
data class FormField(
override val keyName: String,
@StringRes
val name: Int,
override val name: Int,
val icon: IIcon,
@StringRes
val placeholder: Int? = null,
@ -421,7 +436,7 @@ object LoginInfo {
val emptyText: Int,
@StringRes
val invalidText: Int,
val errorCodes: Map<Int, Int>,
override val errorCodes: Map<Int, Int>,
@StringRes
val hintText: Int? = null,
@ -430,10 +445,18 @@ object LoginInfo {
val caseMode: CaseMode = CaseMode.UNCHANGED,
val hideText: Boolean = false,
val stripTextRegex: String? = null
) {
) : BaseCredential(keyName, name, errorCodes) {
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
}
data class FormCheckbox(
override val keyName: String,
@StringRes
override val name: Int,
val checked: Boolean = false,
override val errorCodes: Map<Int, Int> = mapOf()
) : BaseCredential(keyName, name, errorCodes)
var chooserList: MutableList<Any>? = null
var platformList: MutableMap<Int, List<Platform>> = mutableMapOf()
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-10-16.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingVertical="4dp">
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp"
tools:text="Text" />
<TextView
android:id="@+id/errorText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="32dp"
android:textColor="?colorError"
tools:text="Error text" />
</LinearLayout>
</layout>

View File

@ -1379,4 +1379,5 @@
<string name="home_availability_update">Aktualizuj</string>
<string name="register_unavailable_read_more">Dowiedz się więcej</string>
<string name="settings_register_hide_sticks_from_old">Stara nie zobaczy pał</string>
<string name="login_podlasie_logout_devices">Wyloguj z pozostałych urządzeń</string>
</resources>