[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 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_URL = "https://cpdklaser.zeto.bialystok.pl/api"
const val PODLASIE_API_USER_ENDPOINT = "/pobierzDaneUcznia" 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 org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_PODLASIE 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.PODLASIE_API_USER_ENDPOINT
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.DataPodlasie
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieApi import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieApi
@ -22,10 +23,23 @@ class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) {
private val api = PodlasieApi(data, null) private val api = PodlasieApi(data, null)
init { init {
PodlasieLoginApi(data) {
doLogin()
}
}
private fun doLogin() {
val loginStoreId = data.loginStore.id val loginStoreId = data.loginStore.id
val loginStoreType = LOGIN_TYPE_PODLASIE val loginStoreType = LOGIN_TYPE_PODLASIE
PodlasieLoginApi(data) { 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 -> api.apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
val uuid = json.getString("Uuid") val uuid = json.getString("Uuid")
val login = json.getString("Login") val login = json.getString("Login")
@ -67,5 +81,4 @@ class PodlasieFirstLogin(val data: DataPodlasie, val onSuccess: () -> Unit) {
onSuccess() onSuccess()
} }
} }
}
} }

View File

@ -22,8 +22,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* 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.LoginFormFragmentBinding
import pl.szczodrzynski.edziennik.databinding.LoginFormItemBinding
import pl.szczodrzynski.navlib.colorAttr import pl.szczodrzynski.navlib.colorAttr
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -75,13 +76,14 @@ class LoginFormFragment : Fragment(), CoroutineScope {
b.subTitle.text = platformName ?: app.getString(mode.name) b.subTitle.text = platformName ?: app.getString(mode.name)
b.text.text = platformGuideText ?: app.getString(mode.guideText) 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) { for (credential in mode.credentials) {
if (platformFormFields?.contains(credential.keyName) == false) if (platformFormFields?.contains(credential.keyName) == false)
continue continue
val b = LoginFormItemBinding.inflate(layoutInflater) if (credential is LoginInfo.FormField) {
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
b.textLayout.hint = app.getString(credential.name) b.textLayout.hint = app.getString(credential.name)
if (credential.hideText) { if (credential.hideText) {
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
@ -103,13 +105,32 @@ class LoginFormFragment : Fragment(), CoroutineScope {
this.b.formContainer.addView(b.root) this.b.formContainer.addView(b.root)
credentials[credential] = b credentials[credential] = b
} }
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
}
}
activity.lastError?.let { error -> activity.lastError?.let { error ->
activity.lastError = null activity.lastError = null
startCoroutineTimer(delayMillis = 200L) { startCoroutineTimer(delayMillis = 200L) {
for (credential in credentials) { for (credential in credentials) {
credential.key.errorCodes[error.errorCode]?.let { 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 return@startCoroutineTimer
} }
} }
@ -137,13 +158,14 @@ class LoginFormFragment : Fragment(), CoroutineScope {
var hasErrors = false var hasErrors = false
credentials.forEach { (credential, b) -> credentials.forEach { (credential, b) ->
if (credential is LoginInfo.FormField && b is LoginFormFieldItemBinding) {
var text = b.textEdit.text?.toString() ?: return@forEach var text = b.textEdit.text?.toString() ?: return@forEach
if (!credential.hideText) if (!credential.hideText)
text = text.trim() text = text.trim()
if (credential.caseMode == LoginInfo.Credential.CaseMode.UPPER_CASE) if (credential.caseMode == LoginInfo.FormField.CaseMode.UPPER_CASE)
text = text.toUpperCase(Locale.getDefault()) text = text.toUpperCase(Locale.getDefault())
if (credential.caseMode == LoginInfo.Credential.CaseMode.LOWER_CASE) if (credential.caseMode == LoginInfo.FormField.CaseMode.LOWER_CASE)
text = text.toLowerCase(Locale.getDefault()) text = text.toLowerCase(Locale.getDefault())
credential.stripTextRegex?.let { credential.stripTextRegex?.let {
@ -167,6 +189,12 @@ class LoginFormFragment : Fragment(), CoroutineScope {
payload.putString(credential.keyName, text) payload.putString(credential.keyName, text)
arguments?.putString(credential.keyName, text) arguments?.putString(credential.keyName, text)
} }
if (credential is LoginInfo.FormCheckbox && b is LoginFormCheckboxItemBinding) {
val checked = b.checkbox.isChecked
payload.putBoolean(credential.keyName, checked)
arguments?.putBoolean(credential.keyName, checked)
}
}
if (hasErrors) if (hasErrors)
return@onClick return@onClick

View File

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