diff --git a/app/build.gradle b/app/build.gradle
index ae884511..57b92b4a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -201,6 +201,11 @@ dependencies {
implementation 'com.qifan.powerpermission:powerpermission:1.0.0'
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.0.0'
+
+ implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT'
+ implementation 'pl.droidsonroids:jspoon:1.3.2'
+ implementation "com.squareup.retrofit2:converter-scalars:2.8.1"
+ implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
}
repositories {
mavenCentral()
diff --git a/app/sampledata/vulcan/edu.lublin.eu.png b/app/sampledata/vulcan/edu.lublin.eu.png
new file mode 100644
index 00000000..ece8b925
Binary files /dev/null and b/app/sampledata/vulcan/edu.lublin.eu.png differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1062001b..6e8fc491 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -134,9 +134,6 @@
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Light" />
-
false }
+ LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
+ .withIsPossible { _, loginStore -> loginStore.hasLoginData("webHost") }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
- LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java)
+ /*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java)
.withIsPossible { _, _ -> false }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },
@@ -118,7 +119,7 @@ val vulcanLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, loginStore ->
- if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_NEW else LOGIN_METHOD_NOT_NEEDED
+ if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
}
)
@@ -133,6 +134,7 @@ val idziennikLoginMethods = listOf(
)
const val LOGIN_TYPE_EDUDZIENNIK = 5
+const val LOGIN_MODE_EDUDZIENNIK_WEB = 0
const val LOGIN_METHOD_EDUDZIENNIK_WEB = 100
val edudziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_EDUDZIENNIK, LOGIN_METHOD_EDUDZIENNIK_WEB, EdudziennikLoginWeb::class.java)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt
index 7d4d5c39..aa3fac8d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt
@@ -142,6 +142,12 @@ object Regexes {
val VULCAN_SHIFT_ANNOTATION by lazy {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
}
+ val VULCAN_WEB_PERMISSIONS by lazy {
+ """permissions: '([A-z0-9/=+\-_]+?)'""".toRegex()
+ }
+ val VULCAN_WEB_SYMBOL_VALIDATE by lazy {
+ """[A-z0-9]+""".toRegex(IGNORE_CASE)
+ }
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt
index e47c7842..a93632b1 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt
@@ -59,7 +59,7 @@ class EdudziennikFirstLogin(val data: DataEdudziennik, val onSuccess: () -> Unit
profileList.add(profile)
}
- EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt
index 892c12c8..1a07c609 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt
@@ -89,7 +89,7 @@ class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
profileList.add(profile)
}
- EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt
index d4e834a6..e77dd50d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt
@@ -120,7 +120,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiLogin: String? = null
var apiLogin: String?
get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin }
- set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value }
+ set(value) { profile?.putStudentData("accountLogin", value); mApiLogin = value }
/**
* A Synergia password.
* Used: for login (API Login Method) in Synergia mode.
@@ -129,7 +129,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiPassword: String? = null
var apiPassword: String?
get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword }
- set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value }
+ set(value) { profile?.putStudentData("accountPassword", value); mApiPassword = value }
/**
* A JST login Code.
@@ -138,8 +138,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode }
- set(value) {
- loginStore.putLoginData("accountCode", value); mApiCode = value }
+ set(value) { profile?.putStudentData("accountCode", value); mApiCode = value }
/**
* A JST login PIN.
* Used only during first login in JST mode.
@@ -147,8 +146,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiPin: String? = null
var apiPin: String?
get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin }
- set(value) {
- loginStore.putLoginData("accountPin", value); mApiPin = value }
+ set(value) { profile?.putStudentData("accountPin", value); mApiPin = value }
/**
* A Synergia API access token.
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt
index f693c41a..68f44269 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt
@@ -33,7 +33,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
val accounts = json.getJsonArray("accounts")
if (accounts == null || accounts.size() < 1) {
- EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore))
onSuccess()
return@portalGet
}
@@ -81,7 +81,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
profileList.add(profile)
}
- EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
@@ -116,14 +116,15 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
).apply {
studentData["isPremium"] = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true
studentData["accountId"] = account.getInt("Id") ?: 0
- studentData["accountLogin"] = login
+ studentData["accountLogin"] = data.apiLogin ?: login
+ studentData["accountPassword"] = data.apiPassword
studentData["accountToken"] = data.apiAccessToken
studentData["accountTokenTime"] = data.apiTokenExpiryTime
studentData["accountRefreshToken"] = data.apiRefreshToken
}
profileList.add(profile)
- EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt
index 166a4ae5..5198d155 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt
@@ -146,12 +146,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
}
val error = if (response.code() == 200) null else
json.getJsonArray("errors")?.getString(0)
+ ?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString
error?.let { code ->
when {
code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
// this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set
code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL
+ code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt
index 4857bd09..11b668f9 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/firstlogin/MobidziennikFirstLogin.kt
@@ -85,7 +85,7 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un
profileList.add(profile)
}
- EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt
index 47b9ef23..4b397f5d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt
@@ -17,9 +17,14 @@ import pl.szczodrzynski.edziennik.values
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
+ fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix()
+ && webAuthCookie.isNotNullNorEmpty()
+ && webHost.isNotNullNorEmpty()
+ && webType.isNotNullNorEmpty()
+ && symbol.isNotNullNorEmpty()
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
- && apiCertificateKey.isNotNullNorEmpty()
- && apiCertificatePrivate.isNotNullNorEmpty()
+ && apiFingerprint[symbol].isNotNullNorEmpty()
+ && apiPrivateKey[symbol].isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
@@ -40,7 +45,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
id,
name,
Team.TYPE_CLASS,
- "$schoolName:$name",
+ "$schoolCode:$name",
-1
)
teamList.put(id, teamObject)
@@ -48,7 +53,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
}
}
- override fun generateUserCode() = "$schoolName:$studentId"
+ override fun generateUserCode() = "$schoolCode:$studentId"
/**
* A UONET+ client symbol.
@@ -59,8 +64,8 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
*/
private var mSymbol: String? = null
var symbol: String?
- get() { mSymbol = mSymbol ?: loginStore.getLoginData("deviceSymbol", null); return mSymbol }
- set(value) { loginStore.putLoginData("deviceSymbol", value); mSymbol = value }
+ get() { mSymbol = mSymbol ?: profile?.getStudentData("symbol", null); return mSymbol }
+ set(value) { profile?.putStudentData("symbol", value); mSymbol = value }
/**
* Group symbol/number of the student's school.
@@ -75,16 +80,26 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value }
/**
- * A school ID consisting of the [symbol] and [schoolSymbol].
+ * Short name of the school, used in some places.
+ *
+ * ListaUczniow/JednostkaSprawozdawczaSkrot, e.g. "SP Wilkow"
+ */
+ private var mSchoolShort: String? = null
+ var schoolShort: String?
+ get() { mSchoolShort = mSchoolShort ?: profile?.getStudentData("schoolShort", null); return mSchoolShort }
+ set(value) { profile?.putStudentData("schoolShort", value) ?: return; mSchoolShort = value }
+
+ /**
+ * A school code consisting of the [symbol] and [schoolSymbol].
*
* [symbol]_[schoolSymbol]
*
* e.g. "poznan_000088"
*/
- private var mSchoolName: String? = null
- var schoolName: String?
- get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
- set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value }
+ private var mSchoolCode: String? = null
+ var schoolCode: String?
+ get() { mSchoolCode = mSchoolCode ?: profile?.getStudentData("schoolName", null); return mSchoolCode }
+ set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolCode = value }
/**
* ID of the student.
@@ -163,45 +178,34 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
* After first login only 3 first characters are stored here.
* This is later used to determine the API URL address.
*/
- private var mApiToken: String? = null
- var apiToken: String?
- get() { mApiToken = mApiToken ?: loginStore.getLoginData("deviceToken", null); return mApiToken }
- set(value) { loginStore.putLoginData("deviceToken", value); mApiToken = value }
+ private var mApiToken: Map? = null
+ var apiToken: Map = mapOf()
+ get() { mApiToken = mApiToken ?: loginStore.getLoginData("apiToken", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiToken ?: mapOf() }
+ set(value) { loginStore.putLoginData("apiToken", app.gson.toJson(value)); mApiToken = value }
/**
* A mobile API registration PIN.
*
* After first login, this is removed and/or set to null.
*/
- private var mApiPin: String? = null
- var apiPin: String?
- get() { mApiPin = mApiPin ?: loginStore.getLoginData("devicePin", null); return mApiPin }
- set(value) { loginStore.putLoginData("devicePin", value); mApiPin = value }
+ private var mApiPin: Map? = null
+ var apiPin: Map = mapOf()
+ get() { mApiPin = mApiPin ?: loginStore.getLoginData("apiPin", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPin ?: mapOf() }
+ set(value) { loginStore.putLoginData("apiPin", app.gson.toJson(value)); mApiPin = value }
- private var mApiCertificateKey: String? = null
- var apiCertificateKey: String?
- get() { mApiCertificateKey = mApiCertificateKey ?: loginStore.getLoginData("certificateKey", null); return mApiCertificateKey }
- set(value) { loginStore.putLoginData("certificateKey", value); mApiCertificateKey = value }
+ private var mApiFingerprint: Map? = null
+ var apiFingerprint: Map = mapOf()
+ get() { mApiFingerprint = mApiFingerprint ?: loginStore.getLoginData("apiFingerprint", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiFingerprint ?: mapOf() }
+ set(value) { loginStore.putLoginData("apiFingerprint", app.gson.toJson(value)); mApiFingerprint = value }
- /**
- * This is not meant for normal usage.
- *
- * It provides a backward compatibility (<4.0) in order
- * to migrate and use private keys instead of PFX.
- */
- private var mApiCertificatePfx: String? = null
- var apiCertificatePfx: String?
- get() { mApiCertificatePfx = mApiCertificatePfx ?: loginStore.getLoginData("certificatePfx", null); return mApiCertificatePfx }
- set(value) { loginStore.putLoginData("certificatePfx", value); mApiCertificatePfx = value }
-
- private var mApiCertificatePrivate: String? = null
- var apiCertificatePrivate: String?
- get() { mApiCertificatePrivate = mApiCertificatePrivate ?: loginStore.getLoginData("certificatePrivate", null); return mApiCertificatePrivate }
- set(value) { loginStore.putLoginData("certificatePrivate", value); mApiCertificatePrivate = value }
+ private var mApiPrivateKey: Map? = null
+ var apiPrivateKey: Map = mapOf()
+ get() { mApiPrivateKey = mApiPrivateKey ?: loginStore.getLoginData("apiPrivateKey", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPrivateKey ?: mapOf() }
+ set(value) { loginStore.putLoginData("apiPrivateKey", app.gson.toJson(value)); mApiPrivateKey = value }
val apiUrl: String?
get() {
- val url = when (apiToken?.substring(0, 3)) {
+ val url = when (apiToken[symbol]?.substring(0, 3)) {
"3S1" -> "https://lekcjaplus.vulcan.net.pl"
"TA1" -> "https://uonetplus-komunikacja.umt.tarnow.pl"
"OP1" -> "https://uonetplus-komunikacja.eszkola.opolskie.pl"
@@ -226,4 +230,95 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
get() {
return "$apiUrl$schoolSymbol/"
}
+
+ /* __ __ _ ______ _____ _ _
+ \ \ / / | | | ____/ ____| | | (_)
+ \ \ /\ / /__| |__ | |__ | (___ | | ___ __ _ _ _ __
+ \ \/ \/ / _ \ '_ \ | __| \___ \ | | / _ \ / _` | | '_ \
+ \ /\ / __/ |_) | | | ____) | | |___| (_) | (_| | | | | |
+ \/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_|
+ __/ |
+ |__*/
+ /**
+ * Federation Services login type.
+ * This might be one of: cufs, adfs, adfslight.
+ */
+ var webType: String?
+ get() { mWebType = mWebType ?: loginStore.getLoginData("webType", null); return mWebType }
+ set(value) { loginStore.putLoginData("webType", value); mWebType = value }
+ private var mWebType: String? = null
+
+ /**
+ * Web server providing the federation services login.
+ * If this is present, WEB_MAIN login is considered as available.
+ */
+ var webHost: String?
+ get() { mWebHost = mWebHost ?: loginStore.getLoginData("webHost", null); return mWebHost }
+ set(value) { loginStore.putLoginData("webHost", value); mWebHost = value }
+ private var mWebHost: String? = null
+
+ /**
+ * An ID used in ADFS & ADFSLight login types.
+ */
+ var webAdfsId: String?
+ get() { mWebAdfsId = mWebAdfsId ?: loginStore.getLoginData("webAdfsId", null); return mWebAdfsId }
+ set(value) { loginStore.putLoginData("webAdfsId", value); mWebAdfsId = value }
+ private var mWebAdfsId: String? = null
+
+ /**
+ * A domain override for ADFS Light.
+ */
+ var webAdfsDomain: String?
+ get() { mWebAdfsDomain = mWebAdfsDomain ?: loginStore.getLoginData("webAdfsDomain", null); return mWebAdfsDomain }
+ set(value) { loginStore.putLoginData("webAdfsDomain", value); mWebAdfsDomain = value }
+ private var mWebAdfsDomain: String? = null
+
+ var webIsHttpCufs: Boolean
+ get() { mWebIsHttpCufs = mWebIsHttpCufs ?: loginStore.getLoginData("webIsHttpCufs", false); return mWebIsHttpCufs ?: false }
+ set(value) { loginStore.putLoginData("webIsHttpCufs", value); mWebIsHttpCufs = value }
+ private var mWebIsHttpCufs: Boolean? = null
+
+ var webIsScopedAdfs: Boolean
+ get() { mWebIsScopedAdfs = mWebIsScopedAdfs ?: loginStore.getLoginData("webIsScopedAdfs", false); return mWebIsScopedAdfs ?: false }
+ set(value) { loginStore.putLoginData("webIsScopedAdfs", value); mWebIsScopedAdfs = value }
+ private var mWebIsScopedAdfs: Boolean? = null
+
+ var webEmail: String?
+ get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail }
+ set(value) { loginStore.putLoginData("webEmail", value); mWebEmail = value }
+ private var mWebEmail: String? = null
+ var webUsername: String?
+ get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("webUsername", null); return mWebUsername }
+ set(value) { loginStore.putLoginData("webUsername", value); mWebUsername = value }
+ private var mWebUsername: String? = null
+ var webPassword: String?
+ get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("webPassword", null); return mWebPassword }
+ set(value) { loginStore.putLoginData("webPassword", value); mWebPassword = value }
+ private var mWebPassword: String? = null
+
+ /**
+ * Expiry time of a certificate POSTed to a LoginEndpoint of the specific symbol.
+ * If the time passes, the certificate needs to be POSTed again (if valid)
+ * or re-generated.
+ */
+ var webExpiryTime: Long
+ get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L }
+ set(value) { profile?.putStudentData("webExpiryTime", value); mWebExpiryTime = value }
+ private var mWebExpiryTime: Long? = null
+
+ /**
+ * EfebSsoAuthCookie retrieved after posting a certificate
+ */
+ var webAuthCookie: String?
+ get() { mWebAuthCookie = mWebAuthCookie ?: profile?.getStudentData("webAuthCookie", null); return mWebAuthCookie }
+ set(value) { profile?.putStudentData("webAuthCookie", value); mWebAuthCookie = value }
+ private var mWebAuthCookie: String? = null
+
+ /**
+ * Permissions needed to get JSONs from home page
+ */
+ var webPermissions: String?
+ get() { mWebPermissions = mWebPermissions ?: profile?.getStudentData("webPermissions", null); return mWebPermissions }
+ set(value) { profile?.putStudentData("webPermissions", value); mWebPermissions = value }
+ private var mWebPermissions: String? = null
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt
index d1f11822..ab032e0c 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt
@@ -19,6 +19,7 @@ const val ENDPOINT_VULCAN_API_NOTICES = 1070
const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080
const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090
const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100
+const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010
val VulcanFeatures = listOf(
// timetable
@@ -61,6 +62,13 @@ val VulcanFeatures = listOf(
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
},
+ /**
+ * Lucky number - using WEB Main.
+ */
+ Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
+ ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN
+ ), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)).withShouldSync { data -> data.shouldSyncLuckyNumber() },
+
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API,
ENDPOINT_VULCAN_API_DICTIONARIES to LOGIN_METHOD_VULCAN_API
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt
index 172be3c0..c500e3e7 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanApi.kt
@@ -106,11 +106,11 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
Request.builder()
.url(url)
.userAgent(VULCAN_API_USER_AGENT)
- .addHeader("RequestCertificateKey", data.apiCertificateKey)
+ .addHeader("RequestCertificateKey", data.apiFingerprint[data.symbol])
.addHeader("RequestSignatureValue",
try {
signContent(
- data.apiCertificatePrivate ?: "",
+ data.apiPrivateKey[data.symbol] ?: "",
finalPayload.toString()
)
} catch (e: Exception) {e.printStackTrace();""})
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt
index 9ab94c30..491e8ea6 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt
@@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.*
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber
import pl.szczodrzynski.edziennik.utils.Utils
class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
@@ -86,6 +87,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
VulcanApiMessagesSent(data, lastSync, onSuccess)
}
+ ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS -> {
+ data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
+ VulcanWebLuckyNumber(data, lastSync, onSuccess)
+ }
else -> onSuccess(endpointId)
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt
new file mode 100644
index 00000000..39433657
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-17.
+ */
+
+package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import im.wangchao.mhttp.Request
+import im.wangchao.mhttp.Response
+import im.wangchao.mhttp.callback.TextCallbackHandler
+import pl.droidsonroids.jspoon.Jspoon
+import pl.szczodrzynski.edziennik.data.api.*
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
+import pl.szczodrzynski.edziennik.data.api.models.ApiError
+import pl.szczodrzynski.edziennik.get
+import pl.szczodrzynski.edziennik.isNotNullNorBlank
+import pl.szczodrzynski.edziennik.utils.Utils
+import pl.szczodrzynski.edziennik.utils.models.Date
+import java.io.File
+import java.net.HttpURLConnection
+
+open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
+ companion object {
+ const val TAG = "VulcanWebMain"
+ const val WEB_MAIN = 0
+ const val WEB_OLD = 1
+ const val WEB_NEW = 2
+ const val WEB_MESSAGES = 3
+ const val STATE_SUCCESS = 0
+ const val STATE_NO_REGISTER = 1
+ const val STATE_LOGGED_OUT = 2
+ }
+
+ val profileId
+ get() = data.profile?.id ?: -1
+
+ val profile
+ get() = data.profile
+
+ private val certificateAdapter by lazy {
+ Jspoon.create().adapter(CufsCertificate::class.java)
+ }
+
+ fun saveCertificate(xml: String) {
+ val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml")
+ file.writeText(xml)
+ }
+
+ fun readCertificate(): String? {
+ val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml")
+ if (file.canRead())
+ return file.readText()
+ return null
+ }
+
+ fun parseCertificate(xml: String): CufsCertificate {
+ val xmlParsed = xml
+ .replace("<[a-z]+?:".toRegex(), "<")
+ .replace("[a-z]+?:".toRegex(), "")
+ .replace("\\sxmlns.*?=\".+?\"".toRegex(), "")
+
+ return certificateAdapter.fromHtml(xmlParsed).also {
+ it.xml = xml
+ }
+ }
+
+ fun postCertificate(certificate: CufsCertificate, symbol: String, onResult: (symbol: String, state: Int) -> Unit): Boolean {
+ // check if the certificate is valid
+ if (Date.fromIso(certificate.expiryDate) < System.currentTimeMillis())
+ return false
+
+ val callback = object : TextCallbackHandler() {
+ override fun onSuccess(text: String?, response: Response?) {
+ if (response?.headers()?.get("Location")?.contains("LoginEndpoint.aspx") == true
+ || response?.headers()?.get("Location")?.contains("?logout=true") == true) {
+ onResult(symbol, STATE_LOGGED_OUT)
+ return
+ }
+ if (text?.contains("LoginEndpoint.aspx?logout=true") == true) {
+ onResult(symbol, STATE_NO_REGISTER)
+ return
+ }
+ if (!validateCallback(text, response, jsonResponse = false)) {
+ return
+ }
+ data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L
+ onResult(symbol, STATE_SUCCESS)
+ }
+
+ override fun onFailure(response: Response?, throwable: Throwable?) {
+ data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
+ .withResponse(response)
+ .withThrowable(throwable))
+ }
+ }
+
+ Request.builder()
+ .url("https://uonetplus.${data.webHost}/$symbol/LoginEndpoint.aspx")
+ .withClient(data.app.httpLazy)
+ .userAgent(SYSTEM_USER_AGENT)
+ .post()
+ .addParameter("wa", "wsignin1.0")
+ .addParameter("wctx", certificate.targetUrl)
+ .addParameter("wresult", certificate.xml)
+ .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
+ .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
+ .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
+ .allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE)
+ .allowErrorCode(429)
+ .callback(callback)
+ .build()
+ .enqueue()
+
+ return true
+ }
+
+ fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List) -> Unit) {
+ val callback = object : TextCallbackHandler() {
+ override fun onSuccess(text: String?, response: Response?) {
+ if (!validateCallback(text, response, jsonResponse = false) || text == null) {
+ return
+ }
+
+ if (postErrors) {
+ when {
+ text.contains("status absolwenta") -> ERROR_VULCAN_WEB_GRADUATE_ACCOUNT
+ else -> null
+ }?.let { errorCode ->
+ data.error(ApiError(TAG, errorCode)
+ .withResponse(response)
+ .withApiResponse(text))
+ return
+ }
+ }
+
+ data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
+
+ val schoolSymbols = mutableListOf()
+ val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/"
+ var clientIndex = text.indexOf(clientUrl)
+ var count = 0
+ while (clientIndex != -1 && count < 100) {
+ val startIndex = clientIndex + clientUrl.length
+ val endIndex = text.indexOf('/', startIndex = startIndex)
+ val schoolSymbol = text.substring(startIndex, endIndex)
+ schoolSymbols += schoolSymbol
+ clientIndex = text.indexOf(clientUrl, startIndex = endIndex)
+ count++
+ }
+ schoolSymbols.removeAll {
+ it.toLowerCase() == "default"
+ || !it.matches(Regexes.VULCAN_WEB_SYMBOL_VALIDATE)
+ }
+
+ if (postErrors && schoolSymbols.isEmpty()) {
+ data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_SCHOOLS)
+ .withResponse(response)
+ .withApiResponse(text))
+ return
+ }
+
+ onSuccess(text, schoolSymbols)
+ }
+
+ override fun onFailure(response: Response?, throwable: Throwable?) {
+ data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
+ .withResponse(response)
+ .withThrowable(throwable))
+ }
+ }
+
+ Request.builder()
+ .url("https://uonetplus.${data.webHost}/$symbol/Start.mvc/Index")
+ .userAgent(SYSTEM_USER_AGENT)
+ .get()
+ .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
+ .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
+ .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
+ .allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE)
+ .allowErrorCode(429)
+ .callback(callback)
+ .build()
+ .enqueue()
+ }
+
+ private fun validateCallback(text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
+ if (text == null) {
+ data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
+ .withResponse(response))
+ return false
+ }
+
+ if (response?.code() !in 200..302 || (jsonResponse && !text.startsWith("{"))) {
+ when {
+ text.contains("The custom error module") -> ERROR_VULCAN_WEB_429
+ else -> ERROR_VULCAN_WEB_OTHER
+ }.let { errorCode ->
+ data.error(ApiError(TAG, errorCode)
+ .withApiResponse(text)
+ .withResponse(response))
+ return false
+ }
+ }
+
+ val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl")
+ val authCookie = cookies["EfebSsoAuthCookie"]
+ if ((authCookie == null || authCookie == "null") && data.webAuthCookie != null) {
+ data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie)
+ }
+ else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie) {
+ data.webAuthCookie = authCookie
+ }
+ return true
+ }
+
+ fun webGetJson(
+ tag: String,
+ webType: Int,
+ endpoint: String,
+ method: Int = POST,
+ parameters: Map = emptyMap(),
+ onSuccess: (json: JsonObject, response: Response?) -> Unit
+ ) {
+ val url = "https://" + when (webType) {
+ WEB_MAIN -> "uonetplus"
+ WEB_OLD -> "uonetplus-opiekun"
+ WEB_NEW -> "uonetplus-uczen"
+ WEB_MESSAGES -> "uonetplus-uzytkownik"
+ else -> "uonetplus"
+ } + ".${data.webHost}/${data.symbol}/$endpoint"
+
+ Utils.d(tag, "Request: Vulcan/WebMain - $url")
+
+ val payload = JsonObject()
+ parameters.map { (name, value) ->
+ when (value) {
+ is JsonObject -> payload.add(name, value)
+ is JsonArray -> payload.add(name, value)
+ is String -> payload.addProperty(name, value)
+ is Int -> payload.addProperty(name, value)
+ is Long -> payload.addProperty(name, value)
+ is Float -> payload.addProperty(name, value)
+ is Char -> payload.addProperty(name, value)
+ is Boolean -> payload.addProperty(name, value)
+ }
+ }
+
+ val callback = object : TextCallbackHandler() {
+ override fun onSuccess(text: String?, response: Response?) {
+ if (!validateCallback(text, response))
+ return
+
+ try {
+ val json = JsonParser().parse(text).asJsonObject
+ onSuccess(json, response)
+ } catch (e: Exception) {
+ data.error(ApiError(tag, EXCEPTION_VULCAN_WEB_REQUEST)
+ .withResponse(response)
+ .withThrowable(e)
+ .withApiResponse(text))
+ }
+ }
+
+ override fun onFailure(response: Response?, throwable: Throwable?) {
+ data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
+ .withResponse(response)
+ .withThrowable(throwable))
+ }
+ }
+
+ Request.builder()
+ .url(url)
+ .userAgent(SYSTEM_USER_AGENT)
+ .apply {
+ when (method) {
+ GET -> get()
+ POST -> post()
+ }
+ }
+ .setJsonBody(payload)
+ .allowErrorCode(429)
+ .callback(callback)
+ .build()
+ .enqueue()
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt
index 74687a7b..46b52de5 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/api/VulcanApiTimetable.kt
@@ -80,7 +80,7 @@ class VulcanApiTimetable(override val data: DataVulcan,
id,
name,
Team.TYPE_VIRTUAL,
- "${data.schoolName}:$name",
+ "${data.schoolCode}:$name",
teacherId ?: oldTeacherId ?: -1
)
data.teamList[id] = team
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt
new file mode 100644
index 00000000..4d657ac7
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-20.
+ */
+
+package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web
+
+import com.google.gson.annotations.SerializedName
+
+data class HomepageTile(
+ @SerializedName("Nazwa")
+ val name: String?,
+ @SerializedName("Url")
+ val url: String?,
+ @SerializedName("Zawartosc")
+ val children: List
+)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt
new file mode 100644
index 00000000..cbf24c2a
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-20.
+ */
+
+package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web
+
+import pl.szczodrzynski.edziennik.DAY
+import pl.szczodrzynski.edziennik.data.api.VULCAN_WEB_ENDPOINT_LUCKY_NUMBER
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
+import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
+import pl.szczodrzynski.edziennik.data.db.entity.Metadata
+import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
+import pl.szczodrzynski.edziennik.getJsonArray
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.edziennik.utils.models.Time
+import pl.szczodrzynski.edziennik.utils.models.Week
+
+class VulcanWebLuckyNumber(override val data: DataVulcan,
+ override val lastSync: Long?,
+ val onSuccess: (endpointId: Int) -> Unit
+) : VulcanWebMain(data, lastSync) {
+ companion object {
+ const val TAG = "VulcanWebLuckyNumber"
+ }
+
+ init {
+ webGetJson(TAG, WEB_MAIN, VULCAN_WEB_ENDPOINT_LUCKY_NUMBER, parameters = mapOf(
+ "permissions" to data.webPermissions
+ )) { json, _ ->
+ val tiles = json
+ .getJsonArray("data")
+ ?.mapNotNull { data.app.gson.fromJson(it.toString(), HomepageTile::class.java) }
+ ?.flatMap { it.children }
+
+ if (tiles == null) {
+ data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS)
+ onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS)
+ return@webGetJson
+ }
+
+ var nextSync = System.currentTimeMillis() + 1* DAY *1000
+
+ tiles.firstOrNull { it.name == data.schoolShort }?.children?.firstOrNull()?.let { tile ->
+ // "Szczęśliwy numer w dzienniku: 16"
+ return@let tile.name?.substringAfterLast(' ')?.toIntOrNull()?.let { number ->
+ // lucky number present
+ val luckyNumberObject = LuckyNumber(
+ profileId,
+ Date.getToday(),
+ number
+ )
+
+ data.luckyNumberList.add(luckyNumberObject)
+ data.metadataList.add(
+ Metadata(
+ profileId,
+ Metadata.TYPE_LUCKY_NUMBER,
+ luckyNumberObject.date.value.toLong(),
+ true,
+ profile?.empty ?: false
+ ))
+ }
+ } ?: {
+ // no lucky number
+ if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour >= 22) {
+ // working days, after 10PM
+ // consider the lucky number is disabled; sync in 4 days
+ nextSync = System.currentTimeMillis() + 4*DAY*1000
+ }
+ else if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 22) {
+ // working days, before 10PM
+
+ }
+ else {
+ // weekends
+ nextSync = Week.getNearestWeekDayDate(Week.MONDAY).combineWith(Time(5, 0, 0))
+ }
+ }()
+
+ data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS)
+ onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt
index 22f823b1..ed541c69 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt
@@ -6,12 +6,15 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
-import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_STUDENT_LIST
+import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
+import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.models.Date
@@ -21,19 +24,97 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
}
private val api = VulcanApi(data, null)
+ private val web = VulcanWebMain(data, null)
private val profileList = mutableListOf()
+ private val loginStoreId = data.loginStore.id
+ private var firstProfileId = loginStoreId
+ private val tryingSymbols = mutableListOf()
init {
- val loginStoreId = data.loginStore.id
- val loginStoreType = LOGIN_TYPE_VULCAN
- var firstProfileId = loginStoreId
+ if (data.loginStore.mode == LOGIN_MODE_VULCAN_WEB) {
+ VulcanLoginWebMain(data) {
+ val xml = web.readCertificate() ?: run {
+ data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_CERTIFICATE))
+ return@VulcanLoginWebMain
+ }
+ val certificate = web.parseCertificate(xml)
+ if (data.symbol != null && data.symbol != "default") {
+ tryingSymbols += data.symbol ?: "default"
+ }
+ else {
+
+ tryingSymbols += certificate.userInstances
+ }
+
+ checkSymbol(certificate)
+ }
+ }
+ else {
+ registerDevice {
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
+ onSuccess()
+ }
+ }
+ }
+
+ private fun checkSymbol(certificate: CufsCertificate) {
+ if (tryingSymbols.isEmpty()) {
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
+ onSuccess()
+ return
+ }
+
+ val result = web.postCertificate(certificate, tryingSymbols.removeAt(0)) { symbol, state ->
+ when (state) {
+ VulcanWebMain.STATE_NO_REGISTER -> {
+ checkSymbol(certificate)
+ }
+ VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT))
+ VulcanWebMain.STATE_SUCCESS -> {
+ webRegisterDevice(symbol) {
+ checkSymbol(certificate)
+ }
+ }
+ }
+ }
+
+ // postCertificate returns false if the cert is not valid anymore
+ if (!result) {
+ data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED)
+ .withApiResponse(certificate.xml))
+ }
+ }
+
+ private fun webRegisterDevice(symbol: String, onSuccess: () -> Unit) {
+ web.getStartPage(symbol, postErrors = false) { _, schoolSymbols ->
+ if (schoolSymbols.isEmpty()) {
+ onSuccess()
+ return@getStartPage
+ }
+ data.symbol = symbol
+ val schoolSymbol = data.schoolSymbol ?: schoolSymbols.firstOrNull()
+ web.webGetJson(TAG, VulcanWebMain.WEB_NEW, "$schoolSymbol/$VULCAN_WEB_ENDPOINT_REGISTER_DEVICE") { result, _ ->
+ val json = result.getJsonObject("data")
+ data.symbol = symbol
+ data.apiToken = data.apiToken.toMutableMap().also {
+ it[symbol] = json.getString("TokenKey")
+ }
+ data.apiPin = data.apiPin.toMutableMap().also {
+ it[symbol] = json.getString("PIN")
+ }
+ registerDevice(onSuccess)
+ }
+ }
+ }
+
+ private fun registerDevice(onSuccess: () -> Unit) {
VulcanLoginApi(data) {
- api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, response ->
+ api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, _ ->
val students = json.getJsonArray("Data")
if (students == null || students.isEmpty()) {
- EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore))
+ EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore))
onSuccess()
return@apiGet
}
@@ -42,7 +123,8 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
val student = studentEl.asJsonObject
val schoolSymbol = student.getString("JednostkaSprawozdawczaSymbol") ?: return@forEach
- val schoolName = "${data.symbol}_$schoolSymbol"
+ val schoolShort = student.getString("JednostkaSprawozdawczaSkrot") ?: return@forEach
+ val schoolCode = "${data.symbol}_$schoolSymbol"
val studentId = student.getInt("Id") ?: return@forEach
val studentLoginId = student.getInt("UzytkownikLoginId") ?: return@forEach
val studentClassId = student.getInt("IdOddzial") ?: return@forEach
@@ -80,7 +162,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
val profile = Profile(
firstProfileId++,
loginStoreId,
- loginStoreType,
+ LOGIN_TYPE_VULCAN,
studentNameLong,
userLogin,
studentNameLong,
@@ -88,6 +170,8 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
accountName
).apply {
this.studentClassName = studentClassName
+ studentData["symbol"] = data.symbol
+
studentData["studentId"] = studentId
studentData["studentLoginId"] = studentLoginId
studentData["studentClassId"] = studentClassId
@@ -95,7 +179,8 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
studentData["studentSemesterNumber"] = studentSemesterNumber
studentData["semester${studentSemesterNumber}Id"] = studentSemesterId
studentData["schoolSymbol"] = schoolSymbol
- studentData["schoolName"] = schoolName
+ studentData["schoolShort"] = schoolShort
+ studentData["schoolName"] = schoolCode
studentData["currentSemesterEndDate"] = currentSemesterEndDate
}
dateSemester1Start?.let {
@@ -108,7 +193,6 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
profileList.add(profile)
}
- EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt
new file mode 100644
index 00000000..45fc96d2
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-17.
+ */
+
+package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
+
+import pl.droidsonroids.jspoon.annotation.Selector
+
+class CufsCertificate {
+ @Selector(value = "EndpointReference Address")
+ var targetUrl: String = ""
+
+ @Selector(value = "Lifetime Created")
+ var createdDate: String = ""
+
+ @Selector(value = "Lifetime Expires")
+ var expiryDate: String = ""
+
+ @Selector(value = "Attribute[AttributeName=UserInstance] AttributeValue")
+ var userInstances: List = listOf()
+
+ var xml = ""
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt
index 4c11caf3..45c0153d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLogin.kt
@@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
+import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.utils.Utils
@@ -45,6 +46,10 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
}
Utils.d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
+ LOGIN_METHOD_VULCAN_WEB_MAIN -> {
+ data.startProgress(R.string.edziennik_progress_login_vulcan_web_main)
+ VulcanLoginWebMain(data) { onSuccess(loginMethodId) }
+ }
LOGIN_METHOD_VULCAN_API -> {
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
VulcanLoginApi(data) { onSuccess(loginMethodId) }
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt
index 6ff458da..0cf1c895 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginApi.kt
@@ -10,14 +10,11 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.android.getPrivateKeyFromCert
-import pl.szczodrzynski.edziennik.currentTimeUnix
+import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiUpdateSemester
import pl.szczodrzynski.edziennik.data.api.models.ApiError
-import pl.szczodrzynski.edziennik.getJsonObject
-import pl.szczodrzynski.edziennik.getString
-import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
import java.util.*
@@ -34,28 +31,14 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
if (data.studentSemesterNumber == 2 && data.semester2Id == 0)
data.semester2Id = data.studentSemesterNumber
+ copyFromLoginStore()
+
if (data.profile != null && data.isApiLoginValid()) {
onSuccess()
}
else {
- // < v4.0 - PFX to Private Key migration
- if (data.apiCertificatePfx.isNotNullNorEmpty()) {
- try {
- data.apiCertificatePrivate = getPrivateKeyFromCert(
- if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
- data.apiCertificatePfx ?: ""
- )
- data.loginStore.removeLoginData("certificatePfx")
- } catch (e: Throwable) {
- e.printStackTrace()
- } finally {
- onSuccess()
- return@run
- }
- }
-
- if (data.apiCertificateKey.isNotNullNorEmpty()
- && data.apiCertificatePrivate.isNotNullNorEmpty()
+ if (data.apiFingerprint[data.symbol].isNotNullNorEmpty()
+ && data.apiPrivateKey[data.symbol].isNotNullNorEmpty()
&& data.symbol.isNotNullNorEmpty()) {
// (see data.isApiLoginValid())
// the semester end date is over
@@ -63,7 +46,7 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
return@run
}
- if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) {
+ if (data.symbol.isNotNullNorEmpty() && data.apiToken[data.symbol].isNotNullNorEmpty() && data.apiPin[data.symbol].isNotNullNorEmpty()) {
loginWithToken()
}
else {
@@ -72,6 +55,64 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
}
}}
+ private fun copyFromLoginStore() {
+ data.loginStore.data.apply {
+ // < v4.0 - PFX to Private Key migration
+ if (has("certificatePfx")) {
+ try {
+ val privateKey = getPrivateKeyFromCert(
+ if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
+ getString("certificatePfx") ?: ""
+ )
+ data.apiPrivateKey = mapOf(
+ data.symbol to privateKey
+ )
+ remove("certificatePfx")
+ } catch (e: Throwable) {
+ e.printStackTrace()
+ }
+ }
+
+ // 4.0 - new login form - copy user input to profile
+ if (has("symbol")) {
+ data.symbol = getString("symbol")
+ remove("symbol")
+ }
+
+ // 4.0 - before Vulcan Web impl - migrate from strings to Map of Symbol to String
+ if (has("deviceSymbol")) {
+ data.symbol = getString("deviceSymbol")
+ remove("deviceSymbol")
+ }
+ if (has("certificateKey")) {
+ data.apiFingerprint = data.apiFingerprint.toMutableMap().also {
+ it[data.symbol] = getString("certificateKey")
+ }
+ remove("certificateKey")
+ }
+ if (has("certificatePrivate")) {
+ data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also {
+ it[data.symbol] = getString("certificatePrivate")
+ }
+ remove("certificatePrivate")
+ }
+
+ // map form inputs to the symbol
+ if (has("deviceToken")) {
+ data.apiToken = data.apiToken.toMutableMap().also {
+ it[data.symbol] = getString("deviceToken")
+ }
+ remove("deviceToken")
+ }
+ if (has("devicePin")) {
+ data.apiPin = data.apiPin.toMutableMap().also {
+ it[data.symbol] = getString("devicePin")
+ }
+ remove("devicePin")
+ }
+ }
+ }
+
private fun loginWithToken() {
d(TAG, "Request: Vulcan/Login/Api - ${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE")
@@ -123,14 +164,22 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
return
}
- data.apiCertificateKey = cert.getString("CertyfikatKlucz")
- data.apiToken = data.apiToken?.substring(0, 3)
- data.apiCertificatePrivate = getPrivateKeyFromCert(
- if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
+ val privateKey = getPrivateKeyFromCert(
+ if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
cert.getString("CertyfikatPfx") ?: ""
)
+
+ data.apiFingerprint = data.apiFingerprint.toMutableMap().also {
+ it[data.symbol] = cert.getString("CertyfikatKlucz")
+ }
+ data.apiToken = data.apiToken.toMutableMap().also {
+ it[data.symbol] = it[data.symbol]?.substring(0, 3)
+ }
+ data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also {
+ it[data.symbol] = privateKey
+ }
data.loginStore.removeLoginData("certificatePfx")
- data.loginStore.removeLoginData("devicePin")
+ data.loginStore.removeLoginData("apiPin")
onSuccess()
}
@@ -141,14 +190,26 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
}
}
+ val deviceId = data.app.deviceId.padStart(16, '0')
+ val loginStoreId = data.loginStore.id.toString(16).padStart(4, '0')
+ val symbol = data.symbol?.crc16()?.toString(16)?.take(2) ?: "00"
+ val uuid =
+ deviceId.substring(0..7) +
+ "-" + deviceId.substring(8..11) +
+ "-" + deviceId.substring(12..15) +
+ "-" + loginStoreId +
+ "-" + symbol + "6f72616e7a"
+
+ val deviceNameSuffix = " - nie usuwać"
+
Request.builder()
.url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
.userAgent(VULCAN_API_USER_AGENT)
.addHeader("RequestMobileType", "RegisterDevice")
- .addParameter("PIN", data.apiPin)
- .addParameter("TokenKey", data.apiToken)
- .addParameter("DeviceId", UUID.randomUUID().toString())
- .addParameter("DeviceName", VULCAN_API_DEVICE_NAME)
+ .addParameter("PIN", data.apiPin[data.symbol])
+ .addParameter("TokenKey", data.apiToken[data.symbol])
+ .addParameter("DeviceId", uuid)
+ .addParameter("DeviceName", VULCAN_API_DEVICE_NAME.take(50 - deviceNameSuffix.length) + deviceNameSuffix)
.addParameter("DeviceNameUser", "")
.addParameter("DeviceDescription", "")
.addParameter("DeviceSystemType", "Android")
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt
new file mode 100644
index 00000000..467beea3
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
+
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.data.api.*
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
+import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
+import pl.szczodrzynski.edziennik.data.api.models.ApiError
+import pl.szczodrzynski.edziennik.getString
+import pl.szczodrzynski.edziennik.isNotNullNorEmpty
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.fslogin.FSLogin
+import pl.szczodrzynski.fslogin.realm.CufsRealm
+
+class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
+ companion object {
+ private const val TAG = "VulcanLoginWebMain"
+ }
+
+ private val web by lazy { VulcanWebMain(data, null) }
+
+ init { run {
+ copyFromLoginStore()
+
+ if (data.profile != null && data.isWebMainLoginValid()) {
+ onSuccess()
+ }
+ else {
+ if (data.symbol.isNotNullNorEmpty()
+ && data.webType.isNotNullNorEmpty()
+ && data.webHost.isNotNullNorEmpty()
+ && (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty())
+ && data.webPassword.isNotNullNorEmpty()) {
+ try {
+ val success = loginWithCredentials()
+ if (!success)
+ data.error(ApiError(TAG, ERROR_VULCAN_WEB_DATA_MISSING))
+ } catch (e: Exception) {
+ data.error(ApiError(TAG, EXCEPTION_VULCAN_WEB_LOGIN)
+ .withThrowable(e))
+ }
+ }
+ else {
+ data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
+ }
+ }
+ }}
+
+ private fun copyFromLoginStore() {
+ data.loginStore.data.apply {
+ // 4.0 - new login form - copy user input to profile
+ if (has("symbol")) {
+ data.symbol = getString("symbol")
+ remove("symbol")
+ }
+ }
+ }
+
+ private fun loginWithCredentials(): Boolean {
+ val realm = when (data.webType) {
+ "cufs" -> CufsRealm(
+ host = data.webHost ?: return false,
+ symbol = data.symbol ?: "default",
+ httpCufs = data.webIsHttpCufs
+ )
+ "adfs" -> CufsRealm(
+ host = data.webHost ?: return false,
+ symbol = data.symbol ?: "default",
+ httpCufs = data.webIsHttpCufs
+ ).toAdfsRealm(id = data.webAdfsId ?: return false)
+ "adfslight" -> CufsRealm(
+ host = data.webHost ?: return false,
+ symbol = data.symbol ?: "default",
+ httpCufs = data.webIsHttpCufs
+ ).toAdfsLightRealm(
+ id = data.webAdfsId ?: return false,
+ domain = data.webAdfsDomain ?: "adfslight",
+ isScoped = data.webIsScopedAdfs
+ )
+ else -> return false
+ }
+
+ val certificate = web.readCertificate()?.let { web.parseCertificate(it) }
+ if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) {
+ useCertificate(certificate)
+ return true
+ }
+
+ val fsLogin = FSLogin(data.app.http, debug = App.debugMode)
+ fsLogin.performLogin(
+ realm = realm,
+ username = data.webUsername ?: data.webEmail ?: return false,
+ password = data.webPassword ?: return false,
+ onSuccess = { fsCertificate ->
+ web.saveCertificate(fsCertificate.wresult)
+ useCertificate(web.parseCertificate(fsCertificate.wresult))
+ },
+ onFailure = { errorText ->
+ // TODO
+ data.error(ApiError(TAG, 0).withThrowable(RuntimeException(errorText)))
+ }
+ )
+
+ return true
+ }
+
+ private fun useCertificate(certificate: CufsCertificate) {
+ // auto-post certificate when not first login
+ if (data.profile != null && data.symbol != null && data.symbol != "default") {
+ val result = web.postCertificate(certificate, data.symbol ?: "default") { _, state ->
+ when (state) {
+ VulcanWebMain.STATE_SUCCESS -> {
+ web.getStartPage { _, _ -> onSuccess() }
+ }
+ VulcanWebMain.STATE_NO_REGISTER -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_REGISTER))
+ VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT))
+ }
+ }
+ // postCertificate returns false if the cert is not valid anymore
+ if (!result) {
+ data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED)
+ .withApiResponse(certificate.xml))
+ }
+ }
+ else {
+ // first login - succeed immediately
+ onSuccess()
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt
index b6c31d84..6bd9aa83 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt
@@ -201,7 +201,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
" - ",
profile.studentClassName,
"${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart + 1}"
- ) + " " + app.getString(if (profile.isParent) R.string.login_summary_account_parent else R.string.login_summary_account_child)
+ ) + " " + app.getString(if (profile.isParent) R.string.account_type_parent else R.string.account_type_child)
db.profileDao().add(profile)
db.loginStoreDao().add(loginStore)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt
index bdd14f33..ae82ff8f 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt
@@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import retrofit2.Response
@@ -73,7 +74,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
suspend inline fun runCatching(errorSnackbar: ErrorSnackbar, crossinline block: SzkolnyApi.() -> T?): T? {
return try {
- withContext(Dispatchers.Default) { block() }
+ withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) }
}
catch (e: Exception) {
errorSnackbar.addError(e.toApiError(TAG)).show()
@@ -82,7 +83,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
}
suspend inline fun runCatching(activity: AppCompatActivity, crossinline block: SzkolnyApi.() -> T?): T? {
return try {
- withContext(Dispatchers.Default) { block() }
+ withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) }
}
catch (e: Exception) {
ErrorDetailsDialog(
@@ -95,7 +96,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
}
inline fun runCatching(block: SzkolnyApi.() -> T, onError: (e: Throwable) -> Unit): T? {
return try {
- block()
+ block.invoke(this@SzkolnyApi)
}
catch (e: Exception) {
onError(e)
@@ -327,4 +328,11 @@ class SzkolnyApi(val app: App) : CoroutineScope {
return parseResponse(response).message
}
+
+ @Throws(Exception::class)
+ fun getPlatforms(registerName: String): List {
+ val response = api.appLoginPlatforms(registerName).execute()
+
+ return parseResponse(response)
+ }
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt
index 0e3dd574..bce3aef0 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt
@@ -6,11 +6,9 @@ package pl.szczodrzynski.edziennik.data.api.szkolny
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.*
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo
import retrofit2.Call
-import retrofit2.http.Body
-import retrofit2.http.GET
-import retrofit2.http.POST
-import retrofit2.http.Query
+import retrofit2.http.*
interface SzkolnyService {
@@ -34,4 +32,7 @@ interface SzkolnyService {
@POST("feedbackMessage")
fun feedbackMessage(@Body request: FeedbackMessageRequest): Call>
+
+ @GET("appLogin/platforms/{registerName}")
+ fun appLoginPlatforms(@Path("registerName") registerName: String): Call>>
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt
index 1442f40c..aa28faf7 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt
@@ -80,7 +80,7 @@ open class Profile(
var dateYearEnd = Date(studentSchoolYearStart + 1, 6, 30)
fun getSemesterStart(semester: Int) = if (semester == 1) dateSemester1Start else dateSemester2Start
fun getSemesterEnd(semester: Int) = if (semester == 1) dateSemester2Start.clone().stepForward(0, 0, -1) else dateYearEnd
- fun dateToSemester(date: Date) = if (date.value >= getSemesterStart(2).value) 2 else 1
+ fun dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1
@delegate:Ignore
val currentSemester by lazy { dateToSemester(Date.getToday()) }
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt
index 7f65279f..c686b857 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
package pl.szczodrzynski.edziennik.ui.modules.login
import android.app.Activity
@@ -14,20 +18,19 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
-import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding
+import pl.szczodrzynski.edziennik.databinding.LoginActivityBinding
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import kotlin.coroutines.CoroutineContext
class LoginActivity : AppCompatActivity(), CoroutineScope {
companion object {
private const val TAG = "LoginActivity"
- @JvmField
- var navOptions: NavOptions? = null
var thisOneIsTricky = 0
}
private val app: App by lazy { applicationContext as App }
- private lateinit var b: ActivityLoginBinding
+ private lateinit var b: LoginActivityBinding
+ lateinit var navOptions: NavOptions
val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) }
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
@@ -36,7 +39,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
get() = job + Dispatchers.Main
var lastError: ApiError? = null
- val profiles = mutableListOf()
+ val profiles = mutableListOf()
val loginStores = mutableListOf()
override fun onBackPressed() {
@@ -50,7 +53,9 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
return
if (destination.id == R.id.loginSyncFragment)
return
- if (destination.id == R.id.loginChooserFragment) {
+ if (destination.id == R.id.loginFinishFragment)
+ return
+ if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) {
setResult(Activity.RESULT_CANCELED)
finish()
return
@@ -83,7 +88,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
.setPopExitAnim(R.anim.slide_out_right)
.build()
- b = ActivityLoginBinding.inflate(layoutInflater)
+ b = LoginActivityBinding.inflate(layoutInflater)
setContentView(b.root)
errorSnackbar.setCoordinator(b.coordinator, b.snackbarAnchor)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt
new file mode 100644
index 00000000..c9ed2d6e
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.R
+import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
+import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
+import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.ModeViewHolder
+import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.RegisterViewHolder
+import kotlin.coroutines.CoroutineContext
+
+class LoginChooserAdapter(
+ val activity: AppCompatActivity,
+ val onModeClick: ((loginType: LoginInfo.Register, loginMode: LoginInfo.Mode) -> Unit)? = null
+) : RecyclerView.Adapter(), CoroutineScope {
+ companion object {
+ private const val TAG = "LoginChooserAdapter"
+ private const val ITEM_TYPE_REGISTER = 0
+ private const val ITEM_TYPE_MODE = 1
+ const val STATE_CLOSED = 0
+ const val STATE_OPENED = 1
+ }
+
+ private val app = activity.applicationContext as App
+ // optional: place the manager here
+
+ private val job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ var items = mutableListOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return when (viewType) {
+ ITEM_TYPE_REGISTER -> RegisterViewHolder(inflater, parent)
+ ITEM_TYPE_MODE -> ModeViewHolder(inflater, parent)
+ else -> throw IllegalArgumentException("Incorrect viewType")
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (items[position]) {
+ is LoginInfo.Register -> ITEM_TYPE_REGISTER
+ is LoginInfo.Mode -> ITEM_TYPE_MODE
+ else -> throw IllegalArgumentException("Incorrect viewType")
+ }
+ }
+
+ private val onClickListener = View.OnClickListener { view ->
+ val model = view.getTag(R.string.tag_key_model)
+ if (model is LoginInfo.Register && model.loginModes.size == 1) {
+ onModeClick?.invoke(model, model.loginModes.first())
+ return@OnClickListener
+ }
+ if (model is LoginInfo.Mode) {
+ val loginInfo = items.firstOrNull {
+ it is LoginInfo.Register && it.loginModes.contains(model)
+ } as? LoginInfo.Register
+ ?: return@OnClickListener
+
+ onModeClick?.invoke(loginInfo, model)
+ return@OnClickListener
+ }
+ if (model !is LoginInfo.Register)
+ return@OnClickListener
+ expandModel(model, view)
+ }
+
+ private fun expandModel(model: LoginInfo.Register, view: View?, notifyAdapter: Boolean = true) {
+ val position = items.indexOf(model)
+ if (position == -1)
+ return
+
+ if (model.state == STATE_CLOSED) {
+
+ val subItems = model.items
+
+ model.state = STATE_OPENED
+ items.addAll(position + 1, subItems)
+ if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size)
+ }
+ else {
+ val start = position + 1
+ var end: Int = items.size
+ for (i in start until items.size) {
+ val model1 = items[i]
+ val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3
+ if (level <= model.level) {
+ end = i
+ break
+ } else {
+ if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) {
+ model1.state = STATE_CLOSED
+ }
+ }
+ }
+
+ if (end != -1) {
+ items.subList(start, end).clear()
+ if (notifyAdapter) notifyItemRangeRemoved(start, end - start)
+ }
+
+ model.state = STATE_CLOSED
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val item = items[position]
+ if (holder !is BindableViewHolder<*, *>)
+ return
+
+ holder.itemView.setTag(R.string.tag_key_model, item)
+
+ when {
+ holder is RegisterViewHolder && item is LoginInfo.Register -> holder.onBind(activity, app, item, position, this)
+ holder is ModeViewHolder && item is LoginInfo.Mode -> holder.onBind(activity, app, item, position, this)
+ }
+
+ holder.itemView.setOnClickListener(onClickListener)
+ }
+
+ override fun getItemCount() = items.size
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt
index a522d427..37391924 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt
@@ -1,78 +1,90 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
package pl.szczodrzynski.edziennik.ui.modules.login
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
-import android.os.Process
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.animation.AnimationUtils
-import android.widget.CompoundButton
+import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
-import com.afollestad.materialdialogs.DialogAction
-import com.afollestad.materialdialogs.MaterialDialog
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import androidx.recyclerview.widget.LinearLayoutManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.Bundle
import pl.szczodrzynski.edziennik.R
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginChooserBinding
-import pl.szczodrzynski.edziennik.onChange
+import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
import pl.szczodrzynski.edziennik.onClick
-import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
-import pl.szczodrzynski.edziennik.utils.Anim
-import kotlin.system.exitProcess
+import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
+import kotlin.coroutines.CoroutineContext
-
-class LoginChooserFragment : Fragment() {
+class LoginChooserFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "LoginChooserFragment"
- var fakeLogin = false
}
private lateinit var app: App
private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginChooserBinding
+ private lateinit var b: LoginChooserFragmentBinding
private val nav by lazy { activity.nav }
+ private val job: Job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ // local/private variables go here
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
- b = FragmentLoginChooserBinding.inflate(inflater)
+ b = LoginChooserFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- b.topLogo.onClick {
- if (LoginActivity.thisOneIsTricky <= -1) {
- LoginActivity.thisOneIsTricky = 999
- }
- if (LoginActivity.thisOneIsTricky in 0..7) {
- LoginActivity.thisOneIsTricky++
- if (LoginActivity.thisOneIsTricky == 7) {
- b.topLogo.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.shake));
- if (b.devMode.visibility != View.VISIBLE)
- Anim.expand(b.devMode, 500, null);
- LoginActivity.thisOneIsTricky = 3
- }
+ if (!isAdded) return
+
+ val adapter = LoginChooserAdapter(activity) { loginType, loginMode ->
+ if (loginMode.isPlatformSelection) {
+ nav.navigate(R.id.loginPlatformListFragment, Bundle(
+ "loginType" to loginType.loginType,
+ "loginMode" to loginMode.loginMode
+ ), activity.navOptions)
+ return@LoginChooserAdapter
}
+
+ nav.navigate(R.id.loginFormFragment, Bundle(
+ "loginType" to loginType.loginType,
+ "loginMode" to loginMode.loginMode
+ ), activity.navOptions)
+ }
+
+ LoginInfo.chooserList = LoginInfo.chooserList
+ ?: LoginInfo.list.toMutableList()
+
+ adapter.items = LoginInfo.chooserList!!
+ b.list.adapter = adapter
+ b.list.apply {
+ setHasFixedSize(true)
+ layoutManager = LinearLayoutManager(context)
+ addItemDecoration(SimpleDividerItemDecoration(context))
}
- b.loginMobidziennikLogo.onClick { nav.navigate(R.id.loginMobidziennikFragment, null, LoginActivity.navOptions) }
- b.loginLibrusLogo.onClick { nav.navigate(R.id.loginLibrusFragment, null, LoginActivity.navOptions) }
- b.loginVulcanLogo.onClick { nav.navigate(R.id.loginVulcanFragment, null, LoginActivity.navOptions) }
- b.loginIuczniowieLogo.onClick { nav.navigate(R.id.loginIuczniowieFragment, null, LoginActivity.navOptions) }
- b.loginLibrusJstLogo.onClick { nav.navigate(R.id.loginLibrusJstFragment, null, LoginActivity.navOptions) }
- b.loginEdudziennikLogo.onClick { nav.navigate(R.id.loginEdudziennikFragment, null, LoginActivity.navOptions) }
when {
activity.loginStores.isNotEmpty() -> {
// we are navigated here from LoginSummary
- b.cancelButton.visibility = View.VISIBLE
+ b.cancelButton.isVisible = true
b.cancelButton.onClick { nav.navigateUp() }
}
app.config.loginFinished -> {
// we are navigated here from AppDrawer
- b.cancelButton.visibility = View.VISIBLE
+ b.cancelButton.isVisible = true
b.cancelButton.onClick {
activity.setResult(Activity.RESULT_CANCELED)
activity.finish()
@@ -80,57 +92,8 @@ class LoginChooserFragment : Fragment() {
}
else -> {
// there are no profiles
- b.cancelButton.visibility = View.GONE
+ b.cancelButton.isVisible = false
}
}
-
- b.devMode.visibility = if (App.debugMode) View.VISIBLE else View.GONE
- b.devMode.isChecked = app.config.debugMode
- b.devMode.onChange { v, isChecked ->
- if (isChecked) {
- MaterialDialog.Builder(activity)
- .title(R.string.are_you_sure)
- .content(R.string.dev_mode_enable_warning)
- .positiveText(R.string.yes)
- .negativeText(R.string.no)
- .onPositive { _: MaterialDialog?, _: DialogAction? ->
- app.config.debugMode = true
- App.devMode = true
- MaterialAlertDialogBuilder(activity)
- .setTitle("Restart")
- .setMessage("Wymagany restart aplikacji")
- .setPositiveButton("OK") { _, _ ->
- Process.killProcess(Process.myPid())
- Runtime.getRuntime().exit(0)
- exitProcess(0)
- }
- .setCancelable(false)
- .show()
- }
- .onNegative { _: MaterialDialog?, _: DialogAction? ->
- app.config.debugMode = false
- App.devMode = false
- b.devMode.isChecked = app.config.debugMode
- b.devMode.jumpDrawablesToCurrentState()
- Anim.collapse(b.devMode, 1000, null)
- }
- .show()
- } else {
- app.config.debugMode = false
- App.devMode = false
- /*if (b.devModeLayout.getVisibility() === View.VISIBLE) {
- Anim.collapse(b.devModeTitle, 500, null)
- Anim.collapse(b.devModeLayout, 500, null)
- }*/
- }
- }
-
- b.fakeLogin.visibility = if (App.devMode) View.VISIBLE else View.GONE
- b.fakeLogin.isChecked = fakeLogin
- b.fakeLogin.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
- fakeLogin = isChecked
- }
-
- b.helpButton.onClick { startActivity(Intent(activity, FeedbackActivity::class.java)) }
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt
deleted file mode 100644
index be1a83a7..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.animation.ArgbEvaluator
-import android.animation.ObjectAnimator
-import android.graphics.Color
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginEdudziennikBinding
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-
-class LoginEdudziennikFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginEdudziennikFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginEdudziennikBinding
- private val nav by lazy { activity.nav }
- private var hehe = 0
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginEdudziennikBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN ->
- b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password)
- }
- }
- }
-
- b.topText.onClick {
- if (LoginActivity.thisOneIsTricky != -1)
- return@onClick
- hehe++
- if (hehe >= 5) {
- LoginActivity.thisOneIsTricky = 3
- val colorAnim = ObjectAnimator.ofInt(
- b.topText,
- "textColor",
- Color.BLACK,
- Color.RED,
- Color.BLACK,
- Color.RED,
- Color.BLACK,
- Color.RED,
- Color.BLACK
- )
- colorAnim.setEvaluator(ArgbEvaluator())
- colorAnim.duration = 1500L
- colorAnim.start()
- }
- }
-
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginEmailLayout.error = null
- b.loginPasswordLayout.error = null
-
- val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val password = b.loginPassword.text?.toString() ?: ""
-
- if (email.isBlank()) {
- b.loginEmailLayout.error = getString(R.string.login_error_no_email)
- errors = true
- }
- if (password.isBlank()) {
- b.loginPasswordLayout.error = getString(R.string.login_error_no_password)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginEmail.setText(email)
- if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) {
- b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_EDUDZIENNIK,
- "email" to email,
- "password" to password
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt
index 8f52b41a..7b2e66b8 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-4.
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
*/
package pl.szczodrzynski.edziennik.ui.modules.login
@@ -14,7 +14,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginFinishBinding
+import pl.szczodrzynski.edziennik.databinding.LoginFinishFragmentBinding
import kotlin.coroutines.CoroutineContext
class LoginFinishFragment : Fragment(), CoroutineScope {
@@ -24,27 +24,29 @@ class LoginFinishFragment : Fragment(), CoroutineScope {
private lateinit var app: App
private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginFinishBinding
+ private lateinit var b: LoginFinishFragmentBinding
private val nav by lazy { activity.nav }
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
+ // local/private variables go here
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
- b = FragmentLoginFinishBinding.inflate(inflater)
+ b = LoginFinishFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val firstRun = !App.config.loginFinished
- App.config.loginFinished = true
+ val firstRun = !app.config.loginFinished
+ app.config.loginFinished = true
if (!firstRun) {
- b.loginFinishSubtitle.setText(R.string.login_finish_subtitle_not_first_run)
+ b.subTitle.setText(R.string.login_finish_subtitle_not_first_run)
}
b.finishButton.onClick {
@@ -74,4 +76,4 @@ class LoginFinishFragment : Fragment(), CoroutineScope {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt
new file mode 100644
index 00000000..d0676105
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.text.InputType
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.core.widget.addTextChangedListener
+import androidx.fragment.app.Fragment
+import com.google.android.material.textfield.TextInputLayout
+import com.google.gson.JsonParser
+import com.mikepenz.iconics.IconicsDrawable
+import com.mikepenz.iconics.utils.paddingDp
+import com.mikepenz.iconics.utils.sizeDp
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import pl.szczodrzynski.edziennik.*
+import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding
+import pl.szczodrzynski.edziennik.databinding.LoginFormItemBinding
+import pl.szczodrzynski.navlib.colorAttr
+import java.util.*
+import kotlin.coroutines.CoroutineContext
+
+class LoginFormFragment : Fragment(), CoroutineScope {
+ companion object {
+ private const val TAG = "LoginFormFragment"
+ }
+
+ private lateinit var app: App
+ private lateinit var activity: LoginActivity
+ private lateinit var b: LoginFormFragmentBinding
+ private val nav by lazy { activity.nav }
+
+ private val job: Job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ // local/private variables go here
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ activity = (getActivity() as LoginActivity?) ?: return null
+ context ?: return null
+ app = activity.application as App
+ b = LoginFormFragmentBinding.inflate(inflater)
+ return b.root
+ }
+
+ @SuppressLint("ResourceType")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ if (!isAdded) return
+ b.backButton.onClick { nav.navigateUp() }
+
+ b.errorLayout.isVisible = false
+ b.errorLayout.background?.setTintColor(R.attr.colorError.resolveAttr(activity))
+
+ val loginType = arguments?.getInt("loginType") ?: return
+ val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return
+ val loginMode = arguments?.getInt("loginMode") ?: return
+ val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return
+
+ val platformName = arguments?.getString("platformName")
+ val platformGuideText = arguments?.getString("platformGuideText")
+ val platformDescription = arguments?.getString("platformDescription")
+ val platformFormFields = arguments?.getString("platformFormFields")?.split(";")
+ val platformApiData = arguments?.getString("platformApiData")?.let { JsonParser().parse(it)?.asJsonObject }
+
+ b.title.setText(R.string.login_form_title_format, app.getString(register.registerName))
+ b.subTitle.text = platformName ?: app.getString(mode.name)
+ b.text.text = platformGuideText ?: app.getString(mode.guideText)
+
+ val credentials = mutableMapOf()
+
+ 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
+ }
+ 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
+ }
+
+ activity.lastError?.let { error ->
+ activity.lastError = null
+ startCoroutineTimer(delayMillis = 200L) {
+ for (credential in credentials) {
+ credential.key.errorCodes[error.errorCode]?.let {
+ credential.value.textLayout.error = app.getString(it)
+ return@startCoroutineTimer
+ }
+ }
+ mode.errorCodes[error.errorCode]?.let {
+ b.errorText.text = app.getString(it)
+ b.errorLayout.isVisible = true
+ return@startCoroutineTimer
+ }
+ }
+ }
+
+ b.loginButton.onClick {
+ val payload = Bundle(
+ "loginType" to loginType,
+ "loginMode" to loginMode
+ )
+
+ if (App.devMode && b.fakeLogin.isChecked) {
+ payload.putBoolean("fakeLogin", true)
+ }
+
+ platformApiData?.entrySet()?.forEach {
+ payload.putString(it.key, it.value.asString)
+ }
+
+ var hasErrors = false
+ credentials.forEach { (credential, b) ->
+ 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())
+
+ 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)
+ }
+
+ if (hasErrors)
+ return@onClick
+
+ nav.navigate(R.id.loginProgressFragment, payload, activity.navOptions)
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt
new file mode 100644
index 00000000..679bcb68
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import com.google.gson.JsonObject
+import com.mikepenz.iconics.typeface.IIcon
+import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
+import pl.szczodrzynski.edziennik.R
+import pl.szczodrzynski.edziennik.data.api.*
+import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
+
+object LoginInfo {
+
+ private fun getEmailCredential(keyName: String) = Credential(
+ keyName = keyName,
+ name = R.string.login_hint_email,
+ icon = CommunityMaterial.Icon.cmd_at,
+ emptyText = R.string.login_error_no_email,
+ invalidText = R.string.login_error_incorrect_email,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ )
+ private fun getPasswordCredential(keyName: String) = Credential(
+ keyName = keyName,
+ name = R.string.login_hint_password,
+ icon = CommunityMaterial.Icon2.cmd_lock_outline,
+ emptyText = R.string.login_error_no_password,
+ invalidText = R.string.login_error_incorrect_login_or_password,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = ".*",
+ hideText = true
+ )
+
+ val list by lazy { listOf(
+ Register(
+ loginType = LOGIN_TYPE_LIBRUS,
+ internalName = "librus",
+ registerName = R.string.login_register_librus,
+ registerLogo = R.drawable.login_logo_librus,
+ loginModes = listOf(
+ Mode(
+ loginMode = LOGIN_MODE_LIBRUS_EMAIL,
+ name = R.string.login_mode_librus_email,
+ icon = R.drawable.login_mode_librus_email,
+ hintText = R.string.login_mode_librus_email_hint,
+ guideText = R.string.login_mode_librus_email_guide,
+ isRecommended = true,
+ credentials = listOf(
+ getEmailCredential("email"),
+ getPasswordCredential("password")
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED to R.string.login_error_account_not_activated,
+ ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
+ ERROR_CAPTCHA_LIBRUS_PORTAL to R.string.error_3001_reason
+ )
+ ),
+ Mode(
+ loginMode = LOGIN_MODE_LIBRUS_SYNERGIA,
+ name = R.string.login_mode_librus_synergia,
+ icon = R.drawable.login_mode_librus_synergia,
+ hintText = R.string.login_mode_librus_synergia_hint,
+ guideText = R.string.login_mode_librus_synergia_guide,
+ credentials = listOf(
+ Credential(
+ keyName = "accountLogin",
+ name = R.string.login_hint_login,
+ icon = CommunityMaterial.Icon.cmd_account_outline,
+ emptyText = R.string.login_error_no_login,
+ invalidText = R.string.login_error_incorrect_login,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "[A-z0-9._\\-+]+",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ ),
+ getPasswordCredential("accountPassword")
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
+ ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_login_or_password
+ )
+ ),
+ Mode(
+ loginMode = LOGIN_MODE_LIBRUS_JST,
+ name = R.string.login_mode_librus_jst,
+ icon = R.drawable.login_mode_librus_jst,
+ hintText = R.string.login_mode_librus_jst_hint,
+ guideText = R.string.login_mode_librus_jst_guide,
+ credentials = listOf(
+ Credential(
+ keyName = "accountCode",
+ name = R.string.login_hint_token,
+ icon = CommunityMaterial.Icon.cmd_code_braces,
+ emptyText = R.string.login_error_no_token,
+ invalidText = R.string.login_error_incorrect_token,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "[A-Z0-9_]+",
+ caseMode = Credential.CaseMode.UPPER_CASE
+ ),
+ Credential(
+ keyName = "accountPin",
+ name = R.string.login_hint_pin,
+ icon = CommunityMaterial.Icon2.cmd_lock,
+ emptyText = R.string.login_error_no_pin,
+ invalidText = R.string.login_error_incorrect_pin,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "[a-z0-9_]+",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ )
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_code_or_pin,
+ ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_code_or_pin
+ )
+ )
+ )
+ ),
+ Register(
+ loginType = LOGIN_TYPE_VULCAN,
+ internalName = "vulcan",
+ registerName = R.string.login_type_vulcan,
+ registerLogo = R.drawable.login_logo_vulcan,
+ loginModes = listOf(
+ Mode(
+ loginMode = LOGIN_MODE_VULCAN_API,
+ name = R.string.login_mode_vulcan_api,
+ icon = R.drawable.login_mode_vulcan_api,
+ hintText = R.string.login_mode_vulcan_api_hint,
+ guideText = R.string.login_mode_vulcan_api_guide,
+ isRecommended = true,
+ credentials = listOf(
+ Credential(
+ keyName = "deviceToken",
+ name = R.string.login_hint_token,
+ icon = CommunityMaterial.Icon.cmd_code_braces,
+ emptyText = R.string.login_error_no_token,
+ invalidText = R.string.login_error_incorrect_token,
+ errorCodes = mapOf(
+ ERROR_LOGIN_VULCAN_INVALID_TOKEN to R.string.login_error_incorrect_token
+ ),
+ isRequired = true,
+ validationRegex = "[A-Z0-9]{5,12}",
+ caseMode = Credential.CaseMode.UPPER_CASE
+ ),
+ Credential(
+ keyName = "symbol",
+ name = R.string.login_hint_symbol,
+ icon = CommunityMaterial.Icon2.cmd_school,
+ emptyText = R.string.login_error_no_symbol,
+ invalidText = R.string.login_error_incorrect_symbol,
+ errorCodes = mapOf(
+ ERROR_LOGIN_VULCAN_INVALID_SYMBOL to R.string.login_error_incorrect_symbol
+ ),
+ isRequired = true,
+ validationRegex = "[a-z0-9_-]+",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ ),
+ Credential(
+ keyName = "devicePin",
+ name = R.string.login_hint_pin,
+ icon = CommunityMaterial.Icon2.cmd_lock,
+ emptyText = R.string.login_error_no_pin,
+ invalidText = R.string.login_error_incorrect_pin,
+ errorCodes = mapOf(
+ ERROR_LOGIN_VULCAN_INVALID_PIN to R.string.login_error_incorrect_pin
+ ),
+ isRequired = true,
+ validationRegex = "[0-9]+",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ )
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_VULCAN_EXPIRED_TOKEN to R.string.login_error_expired_token
+ )
+ ),
+ Mode(
+ loginMode = LOGIN_MODE_VULCAN_WEB,
+ name = R.string.login_mode_vulcan_web,
+ icon = R.drawable.login_mode_vulcan_web,
+ hintText = R.string.login_mode_vulcan_web_hint,
+ guideText = R.string.login_mode_vulcan_web_guide,
+ isTesting = true,
+ isPlatformSelection = true,
+ credentials = listOf(
+ getEmailCredential("webEmail"),
+ Credential(
+ keyName = "webUsername",
+ name = R.string.login_hint_username,
+ icon = CommunityMaterial.Icon.cmd_account_outline,
+ emptyText = R.string.login_error_no_username,
+ invalidText = R.string.login_error_incorrect_username,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "[A-Z]{7}[0-9]+",
+ caseMode = Credential.CaseMode.UPPER_CASE
+ ),
+ getPasswordCredential("webPassword")
+ ),
+ errorCodes = mapOf()
+ )
+ )
+ ),
+ Register(
+ loginType = LOGIN_TYPE_MOBIDZIENNIK,
+ internalName = "mobidziennik",
+ registerName = R.string.login_type_mobidziennik,
+ registerLogo = R.drawable.login_logo_mobidziennik,
+ loginModes = listOf(
+ Mode(
+ loginMode = LOGIN_MODE_MOBIDZIENNIK_WEB,
+ name = R.string.login_mode_mobidziennik_web,
+ icon = R.drawable.login_mode_mobidziennik_web,
+ hintText = R.string.login_mode_mobidziennik_web_hint,
+ guideText = R.string.login_mode_mobidziennik_web_guide,
+ credentials = listOf(
+ Credential(
+ keyName = "username",
+ name = R.string.login_hint_login_email,
+ icon = CommunityMaterial.Icon.cmd_account_outline,
+ emptyText = R.string.login_error_no_login,
+ invalidText = R.string.login_error_incorrect_login,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "^[a-z0-9_\\-@+.]+$",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ ),
+ Credential(
+ keyName = "password",
+ name = R.string.login_hint_password,
+ icon = CommunityMaterial.Icon2.cmd_lock_outline,
+ emptyText = R.string.login_error_no_password,
+ invalidText = R.string.login_error_incorrect_login_or_password,
+ errorCodes = mapOf(
+ ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD to R.string.login_error_old_password
+ ),
+ isRequired = true,
+ validationRegex = ".*",
+ hideText = true
+ ),
+ Credential(
+ keyName = "serverName",
+ name = R.string.login_hint_address,
+ icon = CommunityMaterial.Icon2.cmd_web,
+ emptyText = R.string.login_error_no_address,
+ invalidText = R.string.login_error_incorrect_address,
+ errorCodes = mapOf(
+ ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS to R.string.login_error_incorrect_address
+ ),
+ isRequired = true,
+ validationRegex = "^[a-z0-9_\\-]+\$",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ )
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
+ ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED to R.string.sync_error_archived
+ )
+ )
+ )
+ ),
+ Register(
+ loginType = LOGIN_TYPE_IDZIENNIK,
+ internalName = "idziennik",
+ registerName = R.string.login_type_idziennik,
+ registerLogo = R.drawable.login_logo_iuczniowie,
+ loginModes = listOf(
+ Mode(
+ loginMode = LOGIN_MODE_IDZIENNIK_WEB,
+ name = R.string.login_mode_idziennik_web,
+ icon = R.drawable.login_mode_idziennik_web,
+ hintText = R.string.login_mode_idziennik_web_hint,
+ guideText = R.string.login_mode_idziennik_web_guide,
+ credentials = listOf(
+ Credential(
+ keyName = "schoolName",
+ name = R.string.login_hint_school_name,
+ icon = CommunityMaterial.Icon2.cmd_school,
+ emptyText = R.string.login_error_no_school_name,
+ invalidText = R.string.login_error_incorrect_school_name,
+ errorCodes = mapOf(
+ ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME to R.string.login_error_incorrect_school_name
+ ),
+ isRequired = true,
+ validationRegex = "^[a-z0-9_\\-.]+$",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ ),
+ Credential(
+ keyName = "username",
+ name = R.string.login_hint_username,
+ icon = CommunityMaterial.Icon.cmd_account_outline,
+ emptyText = R.string.login_error_no_username,
+ invalidText = R.string.login_error_incorrect_username,
+ errorCodes = mapOf(),
+ isRequired = true,
+ validationRegex = "^[a-z0-9_\\-.]+$",
+ caseMode = Credential.CaseMode.LOWER_CASE
+ ),
+ getPasswordCredential("password")
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password
+ )
+ )
+ )
+ ),
+ Register(
+ loginType = LOGIN_TYPE_EDUDZIENNIK,
+ internalName = "edudziennik",
+ registerName = R.string.login_type_edudziennik,
+ registerLogo = R.drawable.login_logo_edudziennik,
+ loginModes = listOf(
+ Mode(
+ loginMode = LOGIN_MODE_EDUDZIENNIK_WEB,
+ name = R.string.login_mode_edudziennik_web,
+ icon = R.drawable.login_mode_edudziennik_web,
+ hintText = R.string.login_mode_edudziennik_web_hint,
+ guideText = R.string.login_mode_edudziennik_web_guide,
+ credentials = listOf(
+ getEmailCredential("email"),
+ getPasswordCredential("password")
+ ),
+ errorCodes = mapOf(
+ ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password
+ )
+ )
+ )
+ )
+ ) }
+
+ data class Register(
+ val loginType: Int,
+ val internalName: String,
+ val registerName: Int,
+ @DrawableRes
+ val registerLogo: Int,
+
+ val loginModes: List
+ ) : ExpandableItemModel(loginModes.toMutableList()) {
+ override var level = 1
+ }
+
+ data class Mode(
+ val loginMode: Int,
+
+ @StringRes
+ val name: Int,
+ @DrawableRes
+ val icon: Int,
+ @StringRes
+ val hintText: Int? = null,
+ @StringRes
+ val guideText: Int,
+
+ val isRecommended: Boolean = false,
+ val isTesting: Boolean = false,
+ val isPlatformSelection: Boolean = false,
+
+ val credentials: List,
+ val errorCodes: Map
+ )
+
+ data class Platform(
+ val id: Int,
+ val loginType: Int,
+ val loginMode: Int,
+ val name: String,
+ val description: String?,
+ val guideText: String?,
+ val icon: String,
+ val screenshot: String?,
+ val formFields: List,
+ val apiData: JsonObject
+ )
+
+ data class Credential(
+ val keyName: String,
+
+ @StringRes
+ val name: Int,
+ val icon: IIcon,
+ @StringRes
+ val placeholder: Int? = null,
+ @StringRes
+ val emptyText: Int,
+ @StringRes
+ val invalidText: Int,
+ val errorCodes: Map,
+ @StringRes
+ val hintText: Int? = null,
+
+ val isRequired: Boolean = true,
+ val validationRegex: String,
+ val caseMode: CaseMode = CaseMode.UNCHANGED,
+ val hideText: Boolean = false,
+ val stripTextRegex: String? = null
+ ) {
+ enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
+ }
+
+ var chooserList: MutableList? = null
+ var platformList: MutableMap> = mutableMapOf()
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt
deleted file mode 100644
index 379eee1a..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_IDZIENNIK
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-class LoginIuczniowieFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginIuczniowieFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginIuczniowieBinding
- private val nav by lazy { activity.nav }
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginIuczniowieBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME ->
- b.loginSchoolNameLayout.error = getString(R.string.login_error_incorrect_school_name)
- ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED ->
- b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password)
- }
- }
- }
-
- b.helpButton.onClick { nav.navigate(R.id.loginIuczniowieHelpFragment, null, LoginActivity.navOptions) }
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginSchoolNameLayout.error = null
- b.loginUsernameLayout.error = null
- b.loginPasswordLayout.error = null
-
- val schoolName = b.loginSchoolName.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val username = b.loginUsername.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val password = b.loginPassword.text?.toString() ?: ""
-
- if (schoolName.isBlank()) {
- b.loginSchoolNameLayout.error = getString(R.string.login_error_no_school_name)
- errors = true
- }
- if (username.isBlank()) {
- b.loginUsernameLayout.error = getString(R.string.login_error_no_username)
- errors = true
- }
- if (password.isBlank()) {
- b.loginPasswordLayout.error = getString(R.string.login_error_no_password)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginSchoolName.setText(schoolName)
- b.loginUsername.setText(username)
- if (!"[a-z0-9_\\-]+".toRegex().matches(schoolName)) {
- b.loginSchoolNameLayout.error = getString(R.string.login_error_incorrect_school_name)
- errors = true
- }
- if (!"[a-z0-9_\\-]+".toRegex().matches(username)) {
- b.loginUsernameLayout.error = getString(R.string.login_error_incorrect_username)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_IDZIENNIK,
- "schoolName" to schoolName,
- "username" to username,
- "password" to password
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java
deleted file mode 100644
index 7b5c9d96..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package pl.szczodrzynski.edziennik.ui.modules.login;
-
-import androidx.databinding.DataBindingUtil;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.navigation.NavController;
-import androidx.navigation.Navigation;
-import pl.szczodrzynski.edziennik.App;
-import pl.szczodrzynski.edziennik.R;
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieHelpBinding;
-
-public class LoginIuczniowieHelpFragment extends Fragment {
-
- private App app;
- private NavController nav;
- private FragmentLoginIuczniowieHelpBinding b;
- private static final String TAG = "LoginIuczniowieHelp";
-
- public LoginIuczniowieHelpFragment() { }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- if (getActivity() != null) {
- app = (App) getActivity().getApplicationContext();
- nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
- }
- else {
- return null;
- }
- b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_iuczniowie_help, container, false);
- return b.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- assert getContext() != null;
- assert getActivity() != null;
-
- b.backButton.setOnClickListener((v) -> nav.navigateUp());
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt
deleted file mode 100644
index 3cbf29ed..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.annotation.SuppressLint
-import android.graphics.Color
-import android.os.Build
-import android.os.Bundle
-import android.util.Base64
-import android.webkit.JavascriptInterface
-import android.webkit.WebView
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import com.afollestad.materialdialogs.MaterialDialog
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import pl.szczodrzynski.edziennik.R
-import pl.szczodrzynski.edziennik.data.api.LIBRUS_USER_AGENT
-import pl.szczodrzynski.edziennik.utils.Themes
-import pl.szczodrzynski.edziennik.utils.Utils.hexFromColorInt
-import java.nio.charset.Charset
-
-class LoginLibrusCaptchaActivity : AppCompatActivity() {
- companion object {
- private const val TAG = "LoginLibrusCaptchaActivity"
- }
-
- private lateinit var webView: WebView
- private lateinit var dialog: AlertDialog
- private lateinit var jsInterface: CaptchaCallbackInterface
-
- @SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled")
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setTheme(Themes.appThemeNoDisplay)
- setFinishOnTouchOutside(false)
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- WebView.setWebContentsDebuggingEnabled(true)
- }
-
- val base64Content = """
-PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds
-ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj
-cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxjZW50ZXIgaWQ9ImdyIj48L2NlbnRlcj48YnI+PHNjcmlw
-dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCdncicse3NpdGVrZXk6JzZMZjQ4
-bW9VQUFBQUFCOUNsaGR2SHI0NmdSV1ItQ04zMUNYUVBHMlUnLHRoZW1lOidUSEVNRScsY2FsbGJh
-Y2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNrKGUpO30sImV4cGlyZWQtY2FsbGJhY2si
-OmZ1bmN0aW9uKCl7d2luZG93LmlmLmV4cGlyZWRDYWxsYmFjayhlKTt9LCJlcnJvci1jYWxsYmFj
-ayI6ZnVuY3Rpb24oKXt3aW5kb3cuaWYuZXJyb3JDYWxsYmFjayhlKTt9fSk7fTwvc2NyaXB0Pjwv
-Ym9keT48L2h0bWw+"""
-
- val backgroundColor = if (Themes.isDark) 0x424242 else 0xffffff
- val backgroundColorString = hexFromColorInt(backgroundColor)
-
- val htmlContent = Base64.decode(base64Content, Base64.DEFAULT)
- .toString(Charset.defaultCharset())
- .replace("COLOR", backgroundColorString, true)
- .replace("THEME", if (Themes.isDark) "dark" else "light")
-
- jsInterface = object : CaptchaCallbackInterface {
- @JavascriptInterface
- override fun callback(recaptchaResponse: String) {
- MaterialDialog.Builder(this@LoginLibrusCaptchaActivity)
- .title("Captcha checked")
- .content("Response: $recaptchaResponse")
- .positiveText("OK")
- .show()
- }
-
- @JavascriptInterface
- override fun expiredCallback() {
- MaterialDialog.Builder(this@LoginLibrusCaptchaActivity)
- .title("Captcha expired")
- .content("Captcha expired")
- .positiveText("OK")
- .show()
- }
-
- @JavascriptInterface
- override fun errorCallback() {
- MaterialDialog.Builder(this@LoginLibrusCaptchaActivity)
- .title("Captcha error")
- .content("Captcha error")
- .positiveText("OK")
- .show()
- }
- }
-
- webView = WebView(this).apply {
- //setBackgroundColor((backgroundColor.toLong() or 0xff000000).toInt())
- setBackgroundColor(Color.TRANSPARENT)
- settings.javaScriptEnabled = true
- settings.userAgentString = LIBRUS_USER_AGENT
- addJavascriptInterface(jsInterface, "if")
- loadDataWithBaseURL("https://portal.librus.pl/rodzina/login/", htmlContent, "text/html", "UTF-8", null)
- setLayerType(WebView.LAYER_TYPE_SOFTWARE, null)
- }
-
- dialog = MaterialAlertDialogBuilder(this)
- .setTitle(R.string.login_librus_captcha_title)
- .setView(webView)
- .setNegativeButton(R.string.cancel) { dialog, _ ->
- dialog.dismiss()
- finish()
- }
- .setCancelable(false)
- .show()
- }
-
- interface CaptchaCallbackInterface {
- @JavascriptInterface
- fun callback(recaptchaResponse: String)
- @JavascriptInterface
- fun expiredCallback()
- @JavascriptInterface
- fun errorCallback()
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt
deleted file mode 100644
index 11c2bdd2..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-class LoginLibrusFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginLibrusFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginLibrusBinding
- private val nav by lazy { activity.nav }
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginLibrusBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN ->
- b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password)
- ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED ->
- b.loginEmailLayout.error = getString(R.string.login_error_account_not_activated)
- }
- }
- }
-
- b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) }
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginEmailLayout.error = null
- b.loginPasswordLayout.error = null
-
- val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val password = b.loginPassword.text?.toString() ?: ""
-
- if (email.isBlank()) {
- b.loginEmailLayout.error = getString(R.string.login_error_no_email)
- errors = true
- }
- if (password.isBlank()) {
- b.loginPasswordLayout.error = getString(R.string.login_error_no_password)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginEmail.setText(email)
- if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) {
- b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_LIBRUS,
- "email" to email,
- "password" to password
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java
deleted file mode 100644
index 6984498a..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package pl.szczodrzynski.edziennik.ui.modules.login;
-
-import androidx.databinding.DataBindingUtil;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.navigation.NavController;
-import androidx.navigation.Navigation;
-import pl.szczodrzynski.edziennik.App;
-import pl.szczodrzynski.edziennik.R;
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusHelpBinding;
-
-public class LoginLibrusHelpFragment extends Fragment {
-
- private App app;
- private NavController nav;
- private FragmentLoginLibrusHelpBinding b;
- private static final String TAG = "LoginLibrusHelp";
-
- public LoginLibrusHelpFragment() { }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- if (getActivity() != null) {
- app = (App) getActivity().getApplicationContext();
- nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
- }
- else {
- return null;
- }
- b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_librus_help, container, false);
- return b.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- assert getContext() != null;
- assert getActivity() != null;
-
- b.backButton.setOnClickListener((v) -> nav.navigateUp());
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt
deleted file mode 100644
index d4e3d445..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.graphics.Color
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import androidx.fragment.app.Fragment
-import com.mikepenz.iconics.IconicsDrawable
-import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
-import com.mikepenz.iconics.utils.colorInt
-import com.mikepenz.iconics.utils.sizeDp
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST
-import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_LIBRUS_JST
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusJstBinding
-import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-class LoginLibrusJstFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginLibrusJstFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginLibrusJstBinding
- private val nav by lazy { activity.nav }
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginLibrusJstBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN,
- ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST ->
- b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code_or_pin)
- }
- }
- }
-
- b.loginQrScan.setImageDrawable(IconicsDrawable(activity)
- .icon(CommunityMaterial.Icon2.cmd_qrcode_scan)
- .colorInt(Color.BLACK)
- .sizeDp(72))
- b.loginQrScan.onClick {
- QrScannerDialog(activity, { code ->
- b.loginCode.setText(code)
- if (b.loginPin.requestFocus()) {
- activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- }
- })
- }
-
- b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) }
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginCodeLayout.error = null
- b.loginPinLayout.error = null
-
- val code = b.loginCode.text?.toString()?.toUpperCase(Locale.ROOT) ?: ""
- val pin = b.loginPin.text?.toString() ?: ""
-
- if (code.isBlank()) {
- b.loginCodeLayout.error = getString(R.string.login_error_no_code)
- errors = true
- }
- if (pin.isBlank()) {
- b.loginPinLayout.error = getString(R.string.login_error_no_pin)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginCode.setText(code)
- if (!"[A-Z0-9_]+".toRegex().matches(code)) {
- b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code)
- errors = true
- }
- if (!"[a-z0-9_]+".toRegex().matches(pin)) {
- b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_LIBRUS,
- "loginMode" to LOGIN_MODE_LIBRUS_JST,
- "accountCode" to code,
- "accountPin" to pin
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt
deleted file mode 100644
index de0deb3d..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.*
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-class LoginMobidziennikFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginMobidziennikFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginMobidziennikBinding
- private val nav by lazy { activity.nav }
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginMobidziennikBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN ->
- b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password)
- ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD ->
- b.loginPasswordLayout.error = getString(R.string.login_error_old_password)
- ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED ->
- b.loginUsernameLayout.error = getString(R.string.sync_error_archived)
- ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS ->
- b.loginServerAddressLayout.error = getString(R.string.login_error_incorrect_address)
- }
- }
- }
-
- b.helpButton.onClick { nav.navigate(R.id.loginMobidziennikHelpFragment, null, LoginActivity.navOptions) }
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginServerAddressLayout.error = null
- b.loginUsernameLayout.error = null
- b.loginPasswordLayout.error = null
-
- val serverName = b.loginServerAddress.text
- ?.toString()
- ?.toLowerCase(Locale.ROOT)
- ?.replace("(?:http://|www.|mobidziennik\\.pl|wizja\\.net|\\.)".toRegex(), "") ?: ""
- val username = b.loginUsername.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val password = b.loginPassword.text?.toString() ?: ""
-
- if (serverName.isBlank()) {
- b.loginServerAddressLayout.error = getString(R.string.login_error_no_address)
- errors = true
- }
- if (username.isBlank()) {
- b.loginUsernameLayout.error = getString(R.string.login_error_no_login)
- errors = true
- }
- if (password.isBlank()) {
- b.loginPasswordLayout.error = getString(R.string.login_error_no_password)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginServerAddress.setText(serverName)
- b.loginUsername.setText(username)
- if (!"^[a-z0-9_\\-]+$".toRegex().matches(serverName)) {
- b.loginServerAddressLayout.error = getString(R.string.login_error_incorrect_address)
- errors = true
- }
- if (!"^[a-z0-9_\\-@+.]+$".toRegex().matches(username)) {
- b.loginUsernameLayout.error = getString(R.string.login_error_incorrect_login)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_MOBIDZIENNIK,
- "serverName" to serverName,
- "username" to username,
- "password" to password
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java
deleted file mode 100644
index 511f6682..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package pl.szczodrzynski.edziennik.ui.modules.login;
-
-import androidx.databinding.DataBindingUtil;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.navigation.NavController;
-import androidx.navigation.Navigation;
-import pl.szczodrzynski.edziennik.App;
-import pl.szczodrzynski.edziennik.R;
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikHelpBinding;
-
-public class LoginMobidziennikHelpFragment extends Fragment {
-
- private App app;
- private NavController nav;
- private FragmentLoginMobidziennikHelpBinding b;
- private static final String TAG = "LoginMobidziennikHelp";
-
- public LoginMobidziennikHelpFragment() { }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- if (getActivity() != null) {
- app = (App) getActivity().getApplicationContext();
- nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
- }
- else {
- return null;
- }
- b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_mobidziennik_help, container, false);
- return b.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- assert getContext() != null;
- assert getActivity() != null;
-
- b.backButton.setOnClickListener((v) -> nav.navigateUp());
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt
new file mode 100644
index 00000000..5bc80f36
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.onClick
+import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.PlatformViewHolder
+import kotlin.coroutines.CoroutineContext
+
+class LoginPlatformAdapter(
+ val activity: AppCompatActivity,
+ val onPlatformClick: ((platform: LoginInfo.Platform) -> Unit)? = null
+) : RecyclerView.Adapter(), CoroutineScope {
+ companion object {
+ private const val TAG = "LoginPlatformAdapter"
+ }
+
+ private val app = activity.applicationContext as App
+ // optional: place the manager here
+
+ private val job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ var items = listOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlatformViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return PlatformViewHolder(inflater, parent)
+ }
+
+ override fun onBindViewHolder(holder: PlatformViewHolder, position: Int) {
+ val item = items[position]
+ holder.onBind(activity, app, item, position, this)
+ onPlatformClick?.let {
+ holder.b.root.onClick { _ -> it(item) }
+ }
+ }
+
+ override fun getItemCount() = items.size
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt
new file mode 100644
index 00000000..9beb0860
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import pl.szczodrzynski.edziennik.*
+import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
+import pl.szczodrzynski.edziennik.databinding.LoginPlatformListFragmentBinding
+import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
+import kotlin.coroutines.CoroutineContext
+
+class LoginPlatformListFragment : Fragment(), CoroutineScope {
+ companion object {
+ private const val TAG = "LoginPlatformListFragment"
+ }
+
+ private lateinit var app: App
+ private lateinit var activity: LoginActivity
+ private lateinit var b: LoginPlatformListFragmentBinding
+ private val nav by lazy { activity.nav }
+
+ private val job: Job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ // local/private variables go here
+ private val api by lazy { SzkolnyApi(app) }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ activity = (getActivity() as LoginActivity?) ?: return null
+ context ?: return null
+ app = activity.application as App
+ b = LoginPlatformListFragmentBinding.inflate(inflater)
+ return b.root
+ }
+
+ private lateinit var timeoutJob: Job
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ if (!isAdded) return
+ b.backButton.onClick { nav.navigateUp() }
+
+ val loginType = arguments?.getInt("loginType") ?: return
+ val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return
+ val loginMode = arguments?.getInt("loginMode") ?: return
+ val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return
+
+ timeoutJob = startCoroutineTimer(5000L) {
+ b.timeoutText.isVisible = true
+ timeoutJob.cancel()
+ }
+
+ val adapter = LoginPlatformAdapter(activity) { platform ->
+ nav.navigate(R.id.loginFormFragment, Bundle(
+ "loginType" to platform.loginType,
+ "loginMode" to platform.loginMode,
+ "platformName" to platform.name,
+ "platformDescription" to platform.description,
+ "platformFormFields" to platform.formFields.joinToString(";"),
+ "platformApiData" to platform.apiData.toString()
+ ), activity.navOptions)
+ }
+
+ launch {
+ val platforms = LoginInfo.platformList[mode.name]
+ ?: run {
+ api.runCatching(activity) {
+ getPlatforms(register.internalName)
+ } ?: run {
+ nav.navigateUp()
+ return@launch
+ }
+ }
+ LoginInfo.platformList[mode.name] = platforms
+
+ adapter.items = platforms
+ b.list.adapter = adapter
+ b.list.apply {
+ setHasFixedSize(true)
+ layoutManager = LinearLayoutManager(context)
+ addItemDecoration(SimpleDividerItemDecoration(context))
+ }
+ timeoutJob.cancel()
+ b.loadingLayout.isVisible = false
+ b.list.isVisible = true
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java
deleted file mode 100644
index a86cfa86..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package pl.szczodrzynski.edziennik.ui.modules.login;
-
-
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.annotation.NonNull;
-import pl.szczodrzynski.edziennik.data.db.entity.LoginStore;
-import pl.szczodrzynski.edziennik.data.db.entity.Profile;
-
-public class LoginProfileObject {
- LoginStore loginStore = null;
- List profileList = new ArrayList<>();
- List selectedList = new ArrayList<>();
-
- public LoginProfileObject(@NonNull LoginStore loginStore, @NonNull List profileList) {
- this.loginStore = loginStore;
- this.profileList = profileList;
- for (Profile ignored : profileList) {
- selectedList.add(true);
- }
- }
-
- public LoginProfileObject addProfile(Profile profile) {
- profileList.add(profile);
- selectedList.add(true);
- return this;
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt
index 929d9551..930ee367 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
*/
package pl.szczodrzynski.edziennik.ui.modules.login
@@ -27,8 +27,10 @@ import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding
+import pl.szczodrzynski.edziennik.databinding.LoginProgressFragmentBinding
+import pl.szczodrzynski.edziennik.joinNotNullStrings
import kotlin.coroutines.CoroutineContext
+import kotlin.math.max
class LoginProgressFragment : Fragment(), CoroutineScope {
companion object {
@@ -37,22 +39,26 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
private lateinit var app: App
private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginProgressBinding
+ private lateinit var b: LoginProgressFragmentBinding
private val nav by lazy { activity.nav }
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
+ // local/private variables go here
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
- b = FragmentLoginProgressBinding.inflate(inflater)
+ b = LoginProgressFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ if (!isAdded) return
+
val args = arguments ?: run {
activity.error(ApiError(TAG, LOGIN_NO_ARGUMENTS))
nav.navigateUp()
@@ -66,24 +72,26 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
launch {
activity.errorSnackbar.dismiss()
- val firstProfileId = (app.db.profileDao().lastId ?: 0) + 1
+ val maxProfileId = max(
+ app.db.profileDao().lastId ?: 0,
+ activity.profiles.maxBy { it.profile.id }?.profile?.id ?: 0
+ )
val loginType = args.getInt("loginType", -1)
val loginMode = args.getInt("loginMode", 0)
val loginStore = LoginStore(
- id = firstProfileId,
+ id = maxProfileId + 1,
type = loginType,
mode = loginMode
)
loginStore.copyFrom(args)
- if (App.devMode && LoginChooserFragment.fakeLogin) {
- loginStore.putLoginData("fakeLogin", true)
- }
+ loginStore.removeLoginData("loginType")
+ loginStore.removeLoginData("loginMode")
EdziennikTask.firstLogin(loginStore).enqueue(activity)
}
}
- @Subscribe(threadMode = ThreadMode.MAIN)
+ @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onFirstLoginFinishedEvent(event: FirstLoginFinishedEvent) {
if (event.profileList.isEmpty()) {
MaterialAlertDialogBuilder(activity)
@@ -94,10 +102,21 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
.show()
return
}
+
+ // update subnames with school years and class name
+ for (profile in event.profileList) {
+ val schoolYearName = "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart + 1}"
+ profile.subname = joinNotNullStrings(
+ " - ",
+ profile.studentClassName,
+ schoolYearName
+ )
+ }
+
activity.loginStores += event.loginStore
- activity.profiles += event.profileList.map { LoginSummaryProfileAdapter.Item(it) }
+ activity.profiles += event.profileList.map { LoginSummaryAdapter.Item(it) }
activity.errorSnackbar.dismiss()
- nav.navigate(R.id.loginSummaryFragment, null, LoginActivity.navOptions)
+ nav.navigate(R.id.loginSummaryFragment, null, activity.navOptions)
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt
new file mode 100644
index 00000000..bc9cafbf
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.R
+import pl.szczodrzynski.edziennik.data.db.entity.Profile
+import pl.szczodrzynski.edziennik.databinding.LoginSummaryItemBinding
+import pl.szczodrzynski.edziennik.onClick
+import pl.szczodrzynski.edziennik.trigger
+import kotlin.coroutines.CoroutineContext
+
+class LoginSummaryAdapter(
+ val activity: LoginActivity,
+ val onSelectionChanged: ((item: Item) -> Unit)? = null
+) : RecyclerView.Adapter(), CoroutineScope {
+ companion object {
+ private const val TAG = "LoginSummaryAdapter"
+ }
+
+ private val app = activity.applicationContext as App
+ // optional: place the manager here
+
+ private val job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+
+ var items = listOf- ()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return ViewHolder(LoginSummaryItemBinding.inflate(inflater, parent, false))
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = items[position]
+ val b = holder.b
+ val profile = item.profile
+ val loginStore = activity.loginStores.firstOrNull { it.id == profile.loginStoreId }
+ ?: return
+
+ val loginType = loginStore.type
+ val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return
+ val loginMode = loginStore.mode
+ val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return
+
+ b.profileName.text = profile.name
+ b.profileDetails.text = profile.subname
+ b.checkBox.isChecked = item.isSelected
+ b.modeIcon.setImageResource(mode.icon)
+
+ if (profile.isParent) {
+ b.accountType.setText(R.string.account_type_parent)
+ } else {
+ b.accountType.setText(R.string.account_type_child)
+ }
+
+ b.root.onClick {
+ b.checkBox.trigger()
+ }
+ b.checkBox.setOnCheckedChangeListener { _, isChecked ->
+ item.isSelected = isChecked
+ onSelectionChanged?.invoke(item)
+ }
+ }
+
+ override fun getItemCount() = items.size
+
+ class ViewHolder(val b: LoginSummaryItemBinding) : RecyclerView.ViewHolder(b.root)
+
+ class Item(val profile: Profile, var isSelected: Boolean = true)
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt
index b16c0256..63525c45 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
*/
package pl.szczodrzynski.edziennik.ui.modules.login
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginSummaryBinding
+import pl.szczodrzynski.edziennik.databinding.LoginSummaryFragmentBinding
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
@@ -27,27 +27,32 @@ class LoginSummaryFragment : Fragment(), CoroutineScope {
private lateinit var app: App
private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginSummaryBinding
+ private lateinit var b: LoginSummaryFragmentBinding
private val nav by lazy { activity.nav }
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
+ // local/private variables go here
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
- b = FragmentLoginSummaryBinding.inflate(inflater)
+ b = LoginSummaryFragmentBinding.inflate(inflater)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- b.profileListView.apply {
- adapter = LoginSummaryProfileAdapter(activity, activity.profiles) { item ->
- b.finishButton.isEnabled = activity.profiles.any { it.isSelected }
- }
+ val adapter = LoginSummaryAdapter(activity) { _ ->
+ b.finishButton.isEnabled = activity.profiles.any { it.isSelected }
+ }
+
+ adapter.items = activity.profiles
+ b.list.adapter = adapter
+ b.list.apply {
isNestedScrollingEnabled = false
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
@@ -66,7 +71,7 @@ class LoginSummaryFragment : Fragment(), CoroutineScope {
}
b.anotherButton.onClick {
- nav.navigate(R.id.loginChooserFragment, null, LoginActivity.navOptions)
+ nav.navigate(R.id.loginChooserFragment, null, activity.navOptions)
}
b.finishButton.onClick {
@@ -86,7 +91,7 @@ class LoginSummaryFragment : Fragment(), CoroutineScope {
val args = Bundle(
"registrationAllowed" to b.registerMeSwitch.isChecked
)
- nav.navigate(R.id.loginSyncFragment, args, LoginActivity.navOptions)
+ nav.navigate(R.id.loginSyncFragment, args, activity.navOptions)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt
deleted file mode 100644
index 7a74dd76..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.*
-import pl.szczodrzynski.edziennik.data.db.entity.Profile
-import pl.szczodrzynski.edziennik.databinding.RowLoginProfileListItemBinding
-
-class LoginSummaryProfileAdapter(
- val context: Context,
- val items: List
- ,
- val onSelectionChanged: ((item: Item) -> Unit)? = null
-) : RecyclerView.Adapter() {
-
- private val app by lazy { context.applicationContext as App }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val inflater = LayoutInflater.from(parent.context)
- val view = RowLoginProfileListItemBinding.inflate(inflater, parent, false)
- return ViewHolder(view)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val item = items[position]
- val profile = item.profile
- val b = holder.b
-
- b.textView.text = profile.name
- b.checkBox.isChecked = item.isSelected
-
- val registerIcon = when (profile.loginStoreType) {
- LOGIN_TYPE_MOBIDZIENNIK -> R.drawable.logo_mobidziennik
- LOGIN_TYPE_LIBRUS -> R.drawable.logo_librus
- LOGIN_TYPE_IDZIENNIK -> R.drawable.logo_idziennik
- LOGIN_TYPE_VULCAN -> R.drawable.logo_vulcan
- LOGIN_TYPE_EDUDZIENNIK -> R.drawable.logo_edudziennik
- else -> null
- }
- if (registerIcon == null)
- b.registerIcon.visibility = View.GONE
- else {
- b.registerIcon.visibility = View.VISIBLE
- b.registerIcon.setImageResource(registerIcon)
- }
-
- if (profile.isParent) {
- b.accountType.setText(R.string.login_summary_account_parent)
- } else {
- b.accountType.setText(R.string.login_summary_account_child)
- }
-
- val schoolYearName = "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart+1}"
- b.textDetails.text = joinNotNullStrings(
- " - ",
- profile.studentClassName,
- schoolYearName
- )
-
- b.root.onClick {
- b.checkBox.trigger()
- }
- b.checkBox.setOnCheckedChangeListener { _, isChecked ->
- item.isSelected = isChecked
- onSelectionChanged?.invoke(item)
- }
- }
-
- override fun getItemCount() = items.size
-
- class ViewHolder(val b: RowLoginProfileListItemBinding) : RecyclerView.ViewHolder(b.root)
-
- class Item(val profile: Profile, var isSelected: Boolean = true)
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt
index 49977dee..e13130a5 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
+ * Copyright (c) Kuba Szczodrzyński 2020-4-14.
*/
package pl.szczodrzynski.edziennik.ui.modules.login
@@ -14,7 +14,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding
+import pl.szczodrzynski.edziennik.databinding.LoginSyncErrorFragmentBinding
import pl.szczodrzynski.edziennik.onClick
import kotlin.coroutines.CoroutineContext
@@ -25,18 +25,20 @@ class LoginSyncErrorFragment : Fragment(), CoroutineScope {
private lateinit var app: App
private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginSyncErrorBinding
+ private lateinit var b: LoginSyncErrorFragmentBinding
private val nav by lazy { activity.nav }
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
+ // local/private variables go here
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
- b = FragmentLoginSyncErrorBinding.inflate(inflater)
+ b = LoginSyncErrorFragmentBinding.inflate(inflater)
return b.root
}
@@ -44,7 +46,7 @@ class LoginSyncErrorFragment : Fragment(), CoroutineScope {
b.errorDetails.text = activity.lastError?.getStringReason(activity)
activity.lastError = null
b.nextButton.onClick {
- nav.navigate(R.id.loginFinishFragment, arguments, LoginActivity.navOptions)
+ nav.navigate(R.id.loginFinishFragment, arguments, activity.navOptions)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt
index 789bcb07..04029d36 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-16.
+ */
+
package pl.szczodrzynski.edziennik.ui.modules.login
import android.os.Bundle
@@ -19,9 +23,8 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskProgressEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskStartedEvent
-import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_DISABLED
-import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncBinding
+import pl.szczodrzynski.edziennik.data.db.entity.Profile
+import pl.szczodrzynski.edziennik.databinding.LoginSyncFragmentBinding
import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
@@ -32,7 +35,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
private lateinit var app: App
private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginSyncBinding
+ private lateinit var b: LoginSyncFragmentBinding
private val nav: NavController by lazy { Navigation.findNavController(activity, R.id.nav_host_fragment) }
private val job: Job = Job()
@@ -45,7 +48,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
activity = (getActivity() as LoginActivity?) ?: return null
context ?: return null
app = activity.application as App
- b = FragmentLoginSyncBinding.inflate(inflater)
+ b = LoginSyncFragmentBinding.inflate(inflater)
return b.root
}
@@ -56,9 +59,9 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
val registrationAllowed = arguments?.getBoolean("registrationAllowed") ?: false
profiles.forEach {
it.registration = if (registrationAllowed)
- REGISTRATION_ENABLED
+ Profile.REGISTRATION_ENABLED
else
- REGISTRATION_DISABLED
+ Profile.REGISTRATION_DISABLED
app.db.eventTypeDao().addDefaultTypes(activity, it.id)
}
@@ -82,15 +85,15 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
).concat(" ")
}
- @Subscribe(threadMode = ThreadMode.MAIN)
+ @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) {
- nav.navigate(R.id.loginFinishFragment, finishArguments, LoginActivity.navOptions)
+ nav.navigate(R.id.loginFinishFragment, finishArguments, activity.navOptions)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncProgressEvent(event: ApiTaskProgressEvent) {
b.loginSyncProgressBar.progress = event.progress.roundToInt()
- b.loginSyncProgressBar.isIndeterminate = event.progress < 0f
+ b.loginSyncProgressBar.isIndeterminate = event.progress <= 0f
b.loginSyncSubtitle2.text = event.progressText
}
@@ -98,7 +101,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
fun onSyncErrorEvent(event: ApiTaskErrorEvent) {
EventBus.getDefault().removeStickyEvent(event)
activity.error(event.error)
- nav.navigate(R.id.loginSyncErrorFragment, finishArguments, LoginActivity.navOptions)
+ nav.navigate(R.id.loginSyncErrorFragment, finishArguments, activity.navOptions)
}
override fun onStart() {
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt
deleted file mode 100644
index 4264416a..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
-import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_TEMPLATE
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginTemplateBinding
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-class LoginTemplateFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginTemplateFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginTemplateBinding
- private val nav by lazy { activity.nav }
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginTemplateBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN ->
- b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password)
- ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED ->
- b.loginEmailLayout.error = getString(R.string.login_error_account_not_activated)
- }
- }
- }
-
- b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) }
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginEmailLayout.error = null
- b.loginPasswordLayout.error = null
-
- val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val password = b.loginPassword.text?.toString() ?: ""
-
- if (email.isBlank()) {
- b.loginEmailLayout.error = getString(R.string.login_error_no_email)
- errors = true
- }
- if (password.isBlank()) {
- b.loginPasswordLayout.error = getString(R.string.login_error_no_password)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginEmail.setText(email)
- if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) {
- b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_TEMPLATE,
- "email" to email,
- "password" to password
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt
deleted file mode 100644
index 63ea9834..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2020-1-3.
- */
-
-package pl.szczodrzynski.edziennik.ui.modules.login
-
-import android.graphics.Color
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import androidx.fragment.app.Fragment
-import com.mikepenz.iconics.IconicsDrawable
-import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
-import com.mikepenz.iconics.utils.colorInt
-import com.mikepenz.iconics.utils.sizeDp
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import pl.szczodrzynski.edziennik.*
-import pl.szczodrzynski.edziennik.data.api.*
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding
-import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog
-import pl.szczodrzynski.edziennik.utils.Utils
-import java.util.*
-import kotlin.coroutines.CoroutineContext
-
-class LoginVulcanFragment : Fragment(), CoroutineScope {
- companion object {
- private const val TAG = "LoginVulcanFragment"
- }
-
- private lateinit var app: App
- private lateinit var activity: LoginActivity
- private lateinit var b: FragmentLoginVulcanBinding
- private val nav by lazy { activity.nav }
-
- private val job: Job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Main
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- activity = (getActivity() as LoginActivity?) ?: return null
- context ?: return null
- app = activity.application as App
- b = FragmentLoginVulcanBinding.inflate(inflater)
- return b.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- activity.lastError?.let { error ->
- activity.lastError = null
- startCoroutineTimer(delayMillis = 100) {
- when (error.errorCode) {
- ERROR_LOGIN_VULCAN_INVALID_TOKEN ->
- b.loginTokenLayout.error = getString(R.string.login_error_incorrect_token)
- ERROR_LOGIN_VULCAN_EXPIRED_TOKEN ->
- b.loginTokenLayout.error = getString(R.string.login_error_expired_token)
- ERROR_LOGIN_VULCAN_INVALID_SYMBOL ->
- b.loginSymbolLayout.error = getString(R.string.login_error_incorrect_symbol)
- ERROR_LOGIN_VULCAN_INVALID_PIN ->
- b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin)
- }
- }
- }
-
- b.loginQrScan.setImageDrawable(IconicsDrawable(activity)
- .icon(CommunityMaterial.Icon2.cmd_qrcode_scan)
- .colorInt(Color.BLACK)
- .sizeDp(72))
- b.loginQrScan.onClick {
- QrScannerDialog(activity, { code ->
- try {
- val data = Utils.VulcanQrEncryptionUtils.decode(code)
- "CERT#https?://.+?/([A-z]+)/mobile-api#([A-z0-9]+)#ENDCERT".toRegex().find(data)?.let {
- b.loginToken.setText(it[2])
- b.loginSymbol.setText(it[1])
- if (b.loginPin.requestFocus()) {
- activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- }
- }
- }
- catch (_: Exception) {}
- })
- }
-
- b.helpButton.onClick { nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions) }
- b.backButton.onClick { nav.navigateUp() }
-
- b.loginButton.onClick {
- var errors = false
-
- b.loginTokenLayout.error = null
- b.loginSymbolLayout.error = null
- b.loginPinLayout.error = null
-
- val token = b.loginToken.text?.toString()?.toUpperCase(Locale.ROOT) ?: ""
- val symbol = b.loginSymbol.text?.toString()?.toLowerCase(Locale.ROOT) ?: ""
- val pin = b.loginPin.text?.toString() ?: ""
-
- if (token.isBlank()) {
- b.loginTokenLayout.error = getString(R.string.login_error_no_token)
- errors = true
- }
- if (symbol.isBlank()) {
- b.loginSymbolLayout.error = getString(R.string.login_error_no_symbol)
- errors = true
- }
- if (pin.isBlank()) {
- b.loginPinLayout.error = getString(R.string.login_error_no_pin)
- errors = true
- }
- if (errors) return@onClick
-
- errors = false
-
- b.loginToken.setText(token)
- b.loginSymbol.setText(symbol)
- b.loginPin.setText(pin)
- if (!"[A-Z0-9]{5,12}".toRegex().matches(token)) {
- b.loginTokenLayout.error = getString(R.string.login_error_incorrect_token)
- errors = true
- }
- if (!"[a-z0-9_-]+".toRegex().matches(symbol)) {
- b.loginSymbolLayout.error = getString(R.string.login_error_incorrect_symbol)
- errors = true
- }
- if (!"[a-z0-9_]+".toRegex().matches(pin)) {
- b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin)
- errors = true
- }
- if (errors) return@onClick
-
- val args = Bundle(
- "loginType" to LOGIN_TYPE_VULCAN,
- "deviceToken" to token,
- "deviceSymbol" to symbol,
- "devicePin" to pin
- )
- nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions)
- }
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java
deleted file mode 100644
index 0aa899ae..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package pl.szczodrzynski.edziennik.ui.modules.login;
-
-import androidx.databinding.DataBindingUtil;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.navigation.NavController;
-import androidx.navigation.Navigation;
-import pl.szczodrzynski.edziennik.App;
-import pl.szczodrzynski.edziennik.R;
-import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanHelpBinding;
-
-public class LoginVulcanHelpFragment extends Fragment {
-
- private App app;
- private NavController nav;
- private FragmentLoginVulcanHelpBinding b;
- private static final String TAG = "LoginVulcanHelp";
-
- public LoginVulcanHelpFragment() { }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- if (getActivity() != null) {
- app = (App) getActivity().getApplicationContext();
- nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
- }
- else {
- return null;
- }
- b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_vulcan_help, container, false);
- return b.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- assert getContext() != null;
- assert getActivity() != null;
-
- b.backButton.setOnClickListener((v) -> nav.navigateUp());
- }
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt
new file mode 100644
index 00000000..cdb0e7b8
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-10.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login.viewholder
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.databinding.LoginChooserModeItemBinding
+import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginChooserAdapter
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo
+
+class ModeViewHolder(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ val b: LoginChooserModeItemBinding = LoginChooserModeItemBinding.inflate(inflater, parent, false)
+) : RecyclerView.ViewHolder(b.root), BindableViewHolder {
+ companion object {
+ private const val TAG = "ModeViewHolder"
+ }
+
+ override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Mode, position: Int, adapter: LoginChooserAdapter) {
+ b.logo.setImageResource(item.icon)
+ b.name.setText(item.name)
+ if (item.hintText == null) {
+ b.description.isVisible = false
+ }
+ else {
+ b.description.isVisible = true
+ b.description.setText(item.hintText)
+ }
+ b.hint.isVisible = false
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt
new file mode 100644
index 00000000..ea8a063d
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-10.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login.viewholder
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import coil.api.load
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.databinding.LoginPlatformItemBinding
+import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginPlatformAdapter
+
+class PlatformViewHolder(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ val b: LoginPlatformItemBinding = LoginPlatformItemBinding.inflate(inflater, parent, false)
+) : RecyclerView.ViewHolder(b.root), BindableViewHolder {
+ companion object {
+ private const val TAG = "PlatformViewHolder"
+ }
+
+ override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Platform, position: Int, adapter: LoginPlatformAdapter) {
+ b.logo.load(item.icon)
+ b.name.text = item.name
+ b.description.text = item.description
+ b.description.isVisible = item.description != null
+ b.screenshotButton.isVisible = false
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt
new file mode 100644
index 00000000..921041c8
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2020-4-10.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.login.viewholder
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.databinding.LoginChooserItemBinding
+import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginChooserAdapter
+import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo
+
+class RegisterViewHolder(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ val b: LoginChooserItemBinding = LoginChooserItemBinding.inflate(inflater, parent, false)
+) : RecyclerView.ViewHolder(b.root), BindableViewHolder {
+ companion object {
+ private const val TAG = "RegisterViewHolder"
+ }
+
+ override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Register, position: Int, adapter: LoginChooserAdapter) {
+ b.logo.setImageResource(item.registerLogo)
+ b.name.setText(item.registerName)
+ b.description.isVisible = false
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
index c1d32538..88323bc1 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
@@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Locale;
+import java.util.TimeZone;
import pl.szczodrzynski.edziennik.ExtensionsKt;
import pl.szczodrzynski.edziennik.R;
@@ -108,7 +109,11 @@ public class Date implements Comparable {
public static long fromIso(String dateTime) {
try {
- return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19))));
+ Calendar c = Calendar.getInstance();
+ c.set(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(5, 7)) - 1, Integer.parseInt(dateTime.substring(8, 10)), Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19)));
+ c.set(Calendar.MILLISECOND, 0);
+ c.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return c.getTimeInMillis();
}
catch (Exception e) {
return System.currentTimeMillis();
diff --git a/app/src/main/res/drawable/login_mode_edudziennik_web.png b/app/src/main/res/drawable/login_mode_edudziennik_web.png
new file mode 100644
index 00000000..76fc5261
Binary files /dev/null and b/app/src/main/res/drawable/login_mode_edudziennik_web.png differ
diff --git a/app/src/main/res/drawable/login_mode_idziennik_web.png b/app/src/main/res/drawable/login_mode_idziennik_web.png
new file mode 100644
index 00000000..0db940b7
Binary files /dev/null and b/app/src/main/res/drawable/login_mode_idziennik_web.png differ
diff --git a/app/src/main/res/drawable/logo_librus.png b/app/src/main/res/drawable/login_mode_librus_email.png
similarity index 100%
rename from app/src/main/res/drawable/logo_librus.png
rename to app/src/main/res/drawable/login_mode_librus_email.png
diff --git a/app/src/main/res/drawable/login_mode_librus_jst.png b/app/src/main/res/drawable/login_mode_librus_jst.png
new file mode 100644
index 00000000..b0ea579c
Binary files /dev/null and b/app/src/main/res/drawable/login_mode_librus_jst.png differ
diff --git a/app/src/main/res/drawable/logo_synergia.png b/app/src/main/res/drawable/login_mode_librus_synergia.png
similarity index 100%
rename from app/src/main/res/drawable/logo_synergia.png
rename to app/src/main/res/drawable/login_mode_librus_synergia.png
diff --git a/app/src/main/res/drawable/login_mode_mobidziennik_web.png b/app/src/main/res/drawable/login_mode_mobidziennik_web.png
new file mode 100644
index 00000000..5ab3d146
Binary files /dev/null and b/app/src/main/res/drawable/login_mode_mobidziennik_web.png differ
diff --git a/app/src/main/res/drawable/logo_dzienniczek.png b/app/src/main/res/drawable/login_mode_vulcan_api.png
similarity index 100%
rename from app/src/main/res/drawable/logo_dzienniczek.png
rename to app/src/main/res/drawable/login_mode_vulcan_api.png
diff --git a/app/src/main/res/drawable/logo_vulcan.png b/app/src/main/res/drawable/login_mode_vulcan_web.png
similarity index 100%
rename from app/src/main/res/drawable/logo_vulcan.png
rename to app/src/main/res/drawable/login_mode_vulcan_web.png
diff --git a/app/src/main/res/drawable/logo_mobidziennik.png b/app/src/main/res/drawable/logo_mobidziennik.png
deleted file mode 100644
index 0d4d853f..00000000
Binary files a/app/src/main/res/drawable/logo_mobidziennik.png and /dev/null differ
diff --git a/app/src/main/res/layout/activity_grades_editor.xml b/app/src/main/res/layout/activity_grades_editor.xml
deleted file mode 100644
index 7e3a6998..00000000
--- a/app/src/main/res/layout/activity_grades_editor.xml
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 020e3de9..00000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_web_push_config.xml b/app/src/main/res/layout/activity_web_push_config.xml
deleted file mode 100644
index 17e62ec6..00000000
--- a/app/src/main/res/layout/activity_web_push_config.xml
+++ /dev/null
@@ -1,178 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_login_chooser.xml b/app/src/main/res/layout/fragment_login_chooser.xml
deleted file mode 100644
index 8a7c0f77..00000000
--- a/app/src/main/res/layout/fragment_login_chooser.xml
+++ /dev/null
@@ -1,262 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_login_edudziennik.xml b/app/src/main/res/layout/fragment_login_edudziennik.xml
deleted file mode 100644
index 551962b5..00000000
--- a/app/src/main/res/layout/fragment_login_edudziennik.xml
+++ /dev/null
@@ -1,174 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_login_finish.xml b/app/src/main/res/layout/fragment_login_finish.xml
deleted file mode 100644
index 8635620a..00000000
--- a/app/src/main/res/layout/fragment_login_finish.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_iuczniowie.xml b/app/src/main/res/layout/fragment_login_iuczniowie.xml
deleted file mode 100644
index 89686298..00000000
--- a/app/src/main/res/layout/fragment_login_iuczniowie.xml
+++ /dev/null
@@ -1,189 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_iuczniowie_help.xml b/app/src/main/res/layout/fragment_login_iuczniowie_help.xml
deleted file mode 100644
index 1bcd16c9..00000000
--- a/app/src/main/res/layout/fragment_login_iuczniowie_help.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_librus.xml b/app/src/main/res/layout/fragment_login_librus.xml
deleted file mode 100644
index 2ed16cd2..00000000
--- a/app/src/main/res/layout/fragment_login_librus.xml
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_librus_help.xml b/app/src/main/res/layout/fragment_login_librus_help.xml
deleted file mode 100644
index 21a6d88d..00000000
--- a/app/src/main/res/layout/fragment_login_librus_help.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_librus_jst.xml b/app/src/main/res/layout/fragment_login_librus_jst.xml
deleted file mode 100644
index 53390d63..00000000
--- a/app/src/main/res/layout/fragment_login_librus_jst.xml
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_login_migration_sync.xml b/app/src/main/res/layout/fragment_login_migration_sync.xml
deleted file mode 100644
index ab0cce52..00000000
--- a/app/src/main/res/layout/fragment_login_migration_sync.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_mobidziennik.xml b/app/src/main/res/layout/fragment_login_mobidziennik.xml
deleted file mode 100644
index 1eac127d..00000000
--- a/app/src/main/res/layout/fragment_login_mobidziennik.xml
+++ /dev/null
@@ -1,191 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_mobidziennik_help.xml b/app/src/main/res/layout/fragment_login_mobidziennik_help.xml
deleted file mode 100644
index 0355abea..00000000
--- a/app/src/main/res/layout/fragment_login_mobidziennik_help.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_progress.xml b/app/src/main/res/layout/fragment_login_progress.xml
deleted file mode 100644
index fa73a596..00000000
--- a/app/src/main/res/layout/fragment_login_progress.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_summary.xml b/app/src/main/res/layout/fragment_login_summary.xml
deleted file mode 100644
index 26643b57..00000000
--- a/app/src/main/res/layout/fragment_login_summary.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_sync.xml b/app/src/main/res/layout/fragment_login_sync.xml
deleted file mode 100644
index a0b072c6..00000000
--- a/app/src/main/res/layout/fragment_login_sync.xml
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_template.xml b/app/src/main/res/layout/fragment_login_template.xml
deleted file mode 100644
index 2ed16cd2..00000000
--- a/app/src/main/res/layout/fragment_login_template.xml
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_vulcan.xml b/app/src/main/res/layout/fragment_login_vulcan.xml
deleted file mode 100644
index c1668e60..00000000
--- a/app/src/main/res/layout/fragment_login_vulcan.xml
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login_vulcan_help.xml b/app/src/main/res/layout/fragment_login_vulcan_help.xml
deleted file mode 100644
index d60f8362..00000000
--- a/app/src/main/res/layout/fragment_login_vulcan_help.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/login_activity.xml
similarity index 71%
rename from app/src/main/res/layout/activity_login.xml
rename to app/src/main/res/layout/login_activity.xml
index 56d3f169..52bccaec 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/login_activity.xml
@@ -1,4 +1,8 @@
+
+
@@ -8,11 +12,6 @@
android:orientation="vertical"
android:visibility="visible">
-
-
-
+ android:layout_marginBottom="16dp"
+ android:visibility="invisible" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/login_chooser_fragment.xml b/app/src/main/res/layout/login_chooser_fragment.xml
new file mode 100644
index 00000000..90aa1bc8
--- /dev/null
+++ b/app/src/main/res/layout/login_chooser_fragment.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_chooser_item.xml b/app/src/main/res/layout/login_chooser_item.xml
new file mode 100644
index 00000000..3486a504
--- /dev/null
+++ b/app/src/main/res/layout/login_chooser_item.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_chooser_mode_item.xml b/app/src/main/res/layout/login_chooser_mode_item.xml
new file mode 100644
index 00000000..cbfae377
--- /dev/null
+++ b/app/src/main/res/layout/login_chooser_mode_item.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_login_migration.xml b/app/src/main/res/layout/login_finish_fragment.xml
similarity index 51%
rename from app/src/main/res/layout/fragment_login_migration.xml
rename to app/src/main/res/layout/login_finish_fragment.xml
index 2948b9ec..fcd2c29c 100644
--- a/app/src/main/res/layout/fragment_login_migration.xml
+++ b/app/src/main/res/layout/login_finish_fragment.xml
@@ -1,7 +1,12 @@
-
+
+
+
+
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingHorizontal="24dp">
+ tools:srcCompat="@android:drawable/stat_sys_phone_call_forward" />
-
-
-
-
-
-
-
+ tools:text="@string/login_finish_subtitle" />
-
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/login_form_fragment.xml b/app/src/main/res/layout/login_form_fragment.xml
new file mode 100644
index 00000000..80f7bdc4
--- /dev/null
+++ b/app/src/main/res/layout/login_form_fragment.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_form_item.xml b/app/src/main/res/layout/login_form_item.xml
new file mode 100644
index 00000000..3a4a1660
--- /dev/null
+++ b/app/src/main/res/layout/login_form_item.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_form_item_qr.xml b/app/src/main/res/layout/login_form_item_qr.xml
new file mode 100644
index 00000000..ab46a2be
--- /dev/null
+++ b/app/src/main/res/layout/login_form_item_qr.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_platform_item.xml b/app/src/main/res/layout/login_platform_item.xml
new file mode 100644
index 00000000..a0fb08ae
--- /dev/null
+++ b/app/src/main/res/layout/login_platform_item.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_platform_list_fragment.xml b/app/src/main/res/layout/login_platform_list_fragment.xml
new file mode 100644
index 00000000..ebe8c169
--- /dev/null
+++ b/app/src/main/res/layout/login_platform_list_fragment.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_progress_fragment.xml b/app/src/main/res/layout/login_progress_fragment.xml
new file mode 100644
index 00000000..553ce03e
--- /dev/null
+++ b/app/src/main/res/layout/login_progress_fragment.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_summary_fragment.xml b/app/src/main/res/layout/login_summary_fragment.xml
new file mode 100644
index 00000000..1b7aac65
--- /dev/null
+++ b/app/src/main/res/layout/login_summary_fragment.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_summary_item.xml b/app/src/main/res/layout/login_summary_item.xml
new file mode 100644
index 00000000..99f73853
--- /dev/null
+++ b/app/src/main/res/layout/login_summary_item.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_login_sync_error.xml b/app/src/main/res/layout/login_sync_error_fragment.xml
similarity index 56%
rename from app/src/main/res/layout/fragment_login_sync_error.xml
rename to app/src/main/res/layout/login_sync_error_fragment.xml
index 6d629323..fe7cabb3 100644
--- a/app/src/main/res/layout/fragment_login_sync_error.xml
+++ b/app/src/main/res/layout/login_sync_error_fragment.xml
@@ -1,7 +1,12 @@
-
+
+
+
+
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingHorizontal="24dp">
+ tools:srcCompat="@android:drawable/stat_sys_phone_call_forward" />
-
-
-
-
+ tools:text="@string/login_sync_error_subtitle" />
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/login_sync_fragment.xml b/app/src/main/res/layout/login_sync_fragment.xml
new file mode 100644
index 00000000..59b0b09c
--- /dev/null
+++ b/app/src/main/res/layout/login_sync_fragment.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/row_login_profile_list_item.xml b/app/src/main/res/layout/row_login_profile_list_item.xml
index 322fad71..550034a3 100644
--- a/app/src/main/res/layout/row_login_profile_list_item.xml
+++ b/app/src/main/res/layout/row_login_profile_list_item.xml
@@ -66,7 +66,7 @@
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
- tools:srcCompat="@drawable/logo_mobidziennik" />
+ tools:srcCompat="@drawable/login_mode_mobidziennik_web" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/navigation/nav_login.xml b/app/src/main/res/navigation/nav_login.xml
index af47d04f..7feb8fc7 100644
--- a/app/src/main/res/navigation/nav_login.xml
+++ b/app/src/main/res/navigation/nav_login.xml
@@ -1,164 +1,80 @@
+
+
+ android:label="LoginChooserFragment">
+ android:id="@+id/action_loginChooserFragment_to_loginPlatformListFragment"
+ app:destination="@id/loginPlatformListFragment" />
-
-
-
+ android:id="@+id/action_loginChooserFragment_to_loginFormFragment"
+ app:destination="@id/loginFormFragment" />
+ android:id="@+id/loginPlatformListFragment"
+ android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginPlatformListFragment"
+ android:label="LoginPlatformListFragment">
-
+ android:id="@+id/action_loginPlatformListFragment_to_loginFormFragment"
+ app:destination="@id/loginFormFragment" />
+ android:id="@+id/loginFormFragment"
+ android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginFormFragment"
+ android:label="LoginFormFragment">
-
-
-
-
+ android:label="LoginProgressFragment">
-
-
+ android:label="LoginSummaryFragment">
+ android:label="LoginSyncFragment">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:label="LoginSyncErrorFragment">
+
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index 50d657db..e6d90882 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -592,8 +592,8 @@
Logging in…
Choose the e-register which your school uses. Later you\'ll be able to add more accounts using different e-registers.
Which e-register do you use?
- (child)
- (parent)
+ (child)
+ (parent)
Add a student
You have to select at least one profile to save in the app.
No profile selected
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e2000163..1ee557ac 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -641,8 +641,8 @@
Loguję…
Wybierz z jakiego e-dziennika korzysta Twoja szkoła. W późniejszych etapach będziesz mógł dodać kilka kont z różnych e-dzienników.
Z jakiego e-dziennika korzystasz?
- (uczeń)
- (rodzic)
+ (uczeń)
+ (rodzic)
Dodaj ucznia
Musisz wybrać przynajmniej jeden profil, który chcesz zapisać w aplikacji.
Nie wybrałeś żadnego profilu
@@ -1315,4 +1315,41 @@
Liczona do puli?
ID frekwencji
ID rodzaju podstawowego
+ Jaki masz e-dziennik w szkole?
+ Wybierz z jakiego e-dziennika korzysta Twoja szkoła. Jeśli masz kilka kont w różnych dziennikach, będziesz mógł je dodać później.
+ Librus/Synergia
+ Zaloguj używając e-maila
+ Musisz posiadać konto Librus Rodzina
+ Zaloguj używając loginu i hasła
+ Użyj loginu w postaci \"9874123u\"
+ Logowanie przez platformę VULCAN
+ Oświata w Radomiu oraz Innowacyjny Tarnobrzeg
+ Vulcan UONET+
+ Użyj tokenu, symbolu i kodu PIN
+ Zarejestruj urządzenie na stronie dziennika Vulcan
+ Użyj e-maila/nazwy użytkownika i hasła
+ Zaloguj danymi, które podajesz na stronie e-dziennika VULCAN
+ MobiDziennik
+ Zaloguj nazwą serwera, loginem i hasłem
+ Podaj dane, których używasz na stronie e-dziennika
+ W jaki sposób się logujesz do dziennika?
+ Wybierz, który obrazek odpowiada temu, który widzisz podczas logowania na stronie internetowej swojego dziennika.\n\nJeżeli Twoja szkoła nie korzysta z żadnej z tych platform miejskich, wybierz pierwszą opcję.
+ Ładowanie listy e-dzienników…
+ Jeśli trwa to za długo, sprawdź swoje połączenie internetowe i zrestartuj aplikację.
+ Zaloguj się - %s
+ Zaloguj się swoim kontem Librus, które działa w oficjalnej aplikacji Librus oraz na stronie portal.librus.pl, w niebieskim formularzu.\n\nJeśli nie masz konta Librus, możesz je założyć na stronie https://portal.librus.pl/rodzina/register.
+ Podaj login otrzymany od szkoły, którym logujesz się do Synergii (fioletowy formularz).\n\nZalecane jest logowanie kontem Portal Librus (używając e-maila) w poprzednim kroku.
+ Zaloguj się do Librusa na komputerze, wybierz zakładkę Aplikacje Mobilne, następnie wpisz otrzymany Token i PIN poniżej.
+ Zaloguj się do dziennika Vulcan na komputerze, wybierz zakładkę Dostęp Mobilny, kliknij przycisk Zarejestruj urządzenie mobilne. Podaj otrzymany Token, Symbol i PIN w polach poniżej.
+ Podaj dane, którymi logujesz się na stronie internetowej dziennika VULCAN lub na miejskiej platformie.
+ Podaj dane, których używasz do logowania na stronie MobiDziennika. Jako adres serwera możesz wpisać adres strony internetowej, na której masz MobiDziennik.
+ Logowanie do dziennika Vulcan...
+ iDziennik Progman / iUczniowie
+ Zaloguj używając nazwy użytkownika i hasła
+ Podaj dane, których używasz na stronie internetowej e-dziennika
+ Użyj danych, które wpisujesz w formularz na stronie iDziennika. Jeśli nie pamiętasz hasła, wejdź na http://iuczniowie.progman.pl/ i kliknij przycisk \"Zapomniałem hasła\".
+ EduDziennik
+ Zaloguj używając e-maila i hasła
+ Użyj danych, które podajesz na stronie internetowej e-dziennika
+ Podaj adres e-mail i hasło, których używasz do logowania w przeglądarce na stronie EduDziennika.