Merge branch 'develop' into feature/prymus

This commit is contained in:
Kacper Ziubryniewicz 2020-05-11 21:06:18 +02:00
commit 7f4e45c57c
109 changed files with 3149 additions and 4631 deletions

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -134,9 +134,6 @@
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Light" />
<activity android:name=".ui.modules.login.LoginLibrusCaptchaActivity"
android:theme="@android:style/Theme.Dialog"
android:excludeFromRecents="true"/>
<activity android:name=".ui.modules.home.CounterActivity"
android:theme="@style/AppTheme.Black" />
<activity android:name=".ui.modules.feedback.FeedbackActivity"

View File

@ -114,5 +114,7 @@ const val VULCAN_API_ENDPOINT_MESSAGES_ADD = "mobile-api/Uczen.v3.Uczen/DodajWia
const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"
const val VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/WiadomosciZalacznik"
const val VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomoweZalacznik"
const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers"
const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get"
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"

View File

@ -160,6 +160,16 @@ const val ERROR_VULCAN_API_MAINTENANCE = 340
const val ERROR_VULCAN_API_BAD_REQUEST = 341
const val ERROR_VULCAN_API_OTHER = 342
const val ERROR_VULCAN_ATTACHMENT_DOWNLOAD = 343
const val ERROR_VULCAN_WEB_DATA_MISSING = 344
const val ERROR_VULCAN_WEB_429 = 345
const val ERROR_VULCAN_WEB_OTHER = 346
const val ERROR_VULCAN_WEB_NO_CERTIFICATE = 347
const val ERROR_VULCAN_WEB_NO_REGISTER = 348
const val ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED = 349
const val ERROR_VULCAN_WEB_LOGGED_OUT = 350
const val ERROR_VULCAN_WEB_CERTIFICATE_POST_FAILED = 351
const val ERROR_VULCAN_WEB_GRADUATE_ACCOUNT = 352
const val ERROR_VULCAN_WEB_NO_SCHOOLS = 353
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
@ -210,5 +220,7 @@ const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920
const val EXCEPTION_EDUDZIENNIK_FILE_REQUEST = 921
const val ERROR_ONEDRIVE_DOWNLOAD = 930
const val EXCEPTION_VULCAN_WEB_LOGIN = 931
const val EXCEPTION_VULCAN_WEB_REQUEST = 932
const val LOGIN_NO_ARGUMENTS = 1201

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLogi
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb
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.models.LoginMethod
// librus
@ -103,11 +104,11 @@ const val LOGIN_METHOD_VULCAN_WEB_OLD = 300
const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400
const val LOGIN_METHOD_VULCAN_API = 500
val vulcanLoginMethods = listOf(
/*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
.withIsPossible { _, _ -> 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)

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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.

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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<String?, String?>? = null
var apiToken: Map<String?, String?> = 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<String?, String?>? = null
var apiPin: Map<String?, String?> = 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<String?, String?>? = null
var apiFingerprint: Map<String?, String?> = 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<String?, String?>? = null
var apiPrivateKey: Map<String?, String?> = 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
}

View File

@ -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

View File

@ -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();""})

View File

@ -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)
}
}

View File

@ -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<String>) -> 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<String>()
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<String, Any?> = 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()
}
}

View File

@ -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

View File

@ -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<HomepageTile>
)

View File

@ -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)
}
}
}

View File

@ -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<Profile>()
private val loginStoreId = data.loginStore.id
private var firstProfileId = loginStoreId
private val tryingSymbols = mutableListOf<String>()
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()
}
}

View File

@ -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<String> = listOf()
var xml = ""
}

View File

@ -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) }

View File

@ -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")

View File

@ -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()
}
}
}

View File

@ -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)

View File

@ -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 <T> 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 <T> 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 <T> 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<LoginInfo.Platform> {
val response = api.appLoginPlatforms(registerName).execute()
return parseResponse(response)
}
}

View File

@ -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<ApiResponse<FeedbackMessageResponse>>
@GET("appLogin/platforms/{registerName}")
fun appLoginPlatforms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
}

View File

@ -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()) }

View File

@ -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<LoginSummaryProfileAdapter.Item>()
val profiles = mutableListOf<LoginSummaryAdapter.Item>()
val loginStores = mutableListOf<LoginStore>()
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)

View File

@ -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<RecyclerView.ViewHolder>(), 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<Any>()
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
}

View File

@ -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 (!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
}
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
nav.navigate(R.id.loginFormFragment, Bundle(
"loginType" to loginType.loginType,
"loginMode" to loginMode.loginMode
), activity.navOptions)
}
LoginInfo.chooserList = LoginInfo.chooserList
?: LoginInfo.list.toMutableList<Any>()
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)) }
}
}

View File

@ -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)
}
}
}

View File

@ -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 {

View File

@ -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<LoginInfo.Credential, LoginFormItemBinding>()
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)
}
}
}

View File

@ -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<Mode>
) : ExpandableItemModel<Mode>(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<Credential>,
val errorCodes: Map<Int, Int>
)
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<String>,
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<Int, Int>,
@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<Any>? = null
var platformList: MutableMap<Int, List<Platform>> = mutableMapOf()
}

View File

@ -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)
}
}
}

View File

@ -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());
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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());
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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());
}
}

View File

@ -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<PlatformViewHolder>(), 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<LoginInfo.Platform>()
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
}

View File

@ -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
}
}
}

View File

@ -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<Profile> profileList = new ArrayList<>();
List<Boolean> selectedList = new ArrayList<>();
public LoginProfileObject(@NonNull LoginStore loginStore, @NonNull List<Profile> 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;
}
}

View File

@ -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)

View File

@ -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<LoginSummaryAdapter.ViewHolder>(), 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<Item>()
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)
}

View File

@ -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 ->
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)
}
}
}

View File

@ -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<Item>,
val onSelectionChanged: ((item: Item) -> Unit)? = null
) : RecyclerView.Adapter<LoginSummaryProfileAdapter.ViewHolder>() {
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)
}

View File

@ -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)
}
}
}

View File

@ -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() {

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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());
}
}

View File

@ -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<LoginInfo.Mode, LoginChooserAdapter> {
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
}
}

View File

@ -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<LoginInfo.Platform, LoginPlatformAdapter> {
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
}
}

View File

@ -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<LoginInfo.Register, LoginChooserAdapter> {
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
}
}

View File

@ -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<Date> {
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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,194 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<TextView
android:id="@+id/subjectName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:textAppearance="@style/NavView.TextView.Title"
android:textSize="24sp"
tools:text="geografia" />
<TextView
android:id="@+id/semesterName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="Semestr 1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/grades_editor_semester_average_before" />
<TextView
android:id="@+id/averageBefore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="0.63" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="4dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/grades_editor_semester_average_after" />
<TextView
android:id="@+id/averageAfter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="2.75" />
</LinearLayout>
<LinearLayout
android:id="@+id/yearAverageContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/grades_editor_semester_year_average_before" />
<TextView
android:id="@+id/yearAverageBefore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="0.63" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/grades_editor_semester_year_average_after" />
<TextView
android:id="@+id/yearAverageAfter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@drawable/bg_rounded_4dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="@color/black"
android:textStyle="bold"
tools:text="2.75" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:text="@string/grades_editor_modify_help"
android:textStyle="italic" />
<Button
android:id="@+id/addGrade"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:minHeight="0dp"
android:text="@string/grades_editor_add_grade" />
<Button
android:id="@+id/restoreGrades"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:minHeight="0dp"
android:text="@string/grades_editor_restore" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>

View File

@ -1,87 +0,0 @@
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/mainActivityRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
tools:title="Szkolny.eu"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<!--app:popupTheme="@style/ThemeOverlay.AppCompat.Light"-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/fixed_drawer_container"
android:layout_width="300dp"
android:layout_height="match_parent"
android:visibility="gone">
</FrameLayout>
<FrameLayout
android:id="@+id/fragment_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
android:id="@+id/fragment_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch>
<View
android:id="@+id/fragment_elevation"
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@drawable/shadow_right"
android:visibility="visible" />
</FrameLayout>
</LinearLayout>
<View
android:id="@+id/toolbar_elevation"
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="@drawable/shadow_top"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_collapseMode="pin"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,178 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/mainActivityRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<ScrollView
android:id="@+id/webPushConfig"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textView8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Przekazywanie powiadomień pozwala przesyłać wszystkie powiadomienia z tego urządzenia na wszystkie sparowane komputery, widoczne poniżej.\n\nPowiadomienia będą się wyświetlać w przeglądarce, w okienku pop-up, w prawym dolnym rogu ekranu.\n\nLista komputerów docelowych jest niezależna od wybranego profilu ucznia." />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="@dimen/section_margin_top"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/web_push_configured_browsers"
android:textAllCaps="true"
android:textColor="?attr/colorSection"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/section_underline_height"
android:layout_marginTop="2dp"
android:background="?attr/colorSection" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TableLayout
android:id="@+id/browserList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:shrinkColumns="0"
android:stretchColumns="0">
<!--<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="8dp"
android:gravity="center_vertical"
android:text="Chrome 70.0.3538.102 @ Windows 7 64-bit" />
<Button
android:id="@+id/button6"
style="@style/Widget.AppCompat.Button.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:minWidth="0dp"
android:text="Usuń" />
</TableRow>-->
</TableLayout>
<ProgressBar
android:id="@+id/browserListProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible" />
<TextView
android:id="@+id/browserListErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="18sp"
android:textStyle="italic"
android:visibility="visible"
android:text="@string/web_push_no_browsers" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="@dimen/section_margin_top"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/web_push_pair_browser"
android:textAllCaps="true"
android:textColor="?attr/colorSection"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/section_underline_height"
android:layout_marginTop="2dp"
android:background="?attr/colorSection" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="W przeglądarce na komputerze wejdź na stronę\n\nhttp://edziennik.szczodrzynski.pl/wp\n\nUdziel zgody na wysyłanie powiadomień, następnie zeskanuj wygenerowany kod QR.\n\nPod tym samym adresem możesz zarządzać listą sparowanych z przeglądarką urządzeń.\n\nJeśli sparujesz kilka urządzeń z tą samą przeglądarką, możesz otrzymywać te same powiadomienia kilkukrotnie." />
<Button
android:id="@+id/webPushScanNewButton"
style="@style/Widget.AppCompat.Button.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="Skanuj..." />
</LinearLayout>
</LinearLayout>
</ScrollView>
<me.dm7.barcodescanner.zxing.ZXingScannerView
android:id="@+id/qrCodeScanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" >
</me.dm7.barcodescanner.zxing.ZXingScannerView>
</LinearLayout>
</layout>

View File

@ -1,262 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/topLogo"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-school"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_startpage_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_startpage_subtitle" />
<CheckBox
android:id="@+id/devMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:visibility="gone"
android:text="@string/developer_mode"
tools:visibility="visible"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="32dp"
android:layout_marginRight="24dp"
android:baselineAligned="false"
android:orientation="horizontal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginMobidziennikLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_mobidziennik" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginLibrusLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_librus" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:baselineAligned="false"
android:orientation="horizontal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginVulcanLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_vulcan" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginIuczniowieLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_iuczniowie" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:baselineAligned="false"
android:orientation="horizontal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginLibrusJstLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_oswiata" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginEdudziennikLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_edudziennik" />
</FrameLayout>
</LinearLayout>
<Switch
android:id="@+id/fakeLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="Fake login"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cancelButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/cancel"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:text="@string/help"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,174 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kacper Ziubryniewicz 2019-12-23
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/topText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_edudziennik_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_edudziennik_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginEmailLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_email"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_password"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<!--<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />-->
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-flag-checkered"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_finish_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/loginFinishSubtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_finish_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/finishButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:text="@string/done"
android:textAllCaps="false"
android:layout_marginEnd="20dp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,189 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd_account_circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_iuczniowie_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_iuczniowie_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginSchoolNameLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_school_name"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginSchoolName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginUsernameLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_username"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_password"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-help-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_iuczniowie_help_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:autoLink="web"
android:text="@string/login_iuczniowie_help_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/login_iuczniowie_help_subtitle"
android:src="@drawable/login_help_iuczniowie" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,169 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginEmailLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_email"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_password"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-help-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_help_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:autoLink="web"
android:text="@string/login_librus_help_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/login_librus_help_subtitle"
android:src="@drawable/login_help_librus" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,194 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_jst_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_jst_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/login_code_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:hint="@string/login_hint_token"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/login_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/loginQrScan"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:padding="4dp"
android:layout_marginRight="16dp"
android:layout_marginLeft="8dp"
android:scaleType="centerInside"
tools:srcCompat="@tools:sample/avatars"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/login_vulcan_qr" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPinLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_pin"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false"
android:visibility="gone"/>
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,98 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-sync"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_sync_title"
android:textSize="24sp" />
<ProgressBar
android:id="@+id/loginSyncProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="8dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="8dp"
android:indeterminate="false" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/loginSyncSubtitle1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
tools:text="@string/login_sync_subtitle_1_format" />
<TextView
android:id="@+id/loginSyncSubtitle2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
tools:text="@string/login_sync_subtitle_2_format" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,191 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd_account_circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_mobidziennik_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_mobidziennik_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginUsernameLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_login_email"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_password"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginServerAddressLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_address"
app:errorEnabled="true"
app:helperText="@string/login_helper_address"
app:helperTextEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginServerAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-help-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_mobidziennik_help_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_mobidziennik_help_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="@string/login_mobidziennik_help_address"
android:textSize="18sp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/login_mobidziennik_help_address"
android:src="@drawable/login_help_mobidziennik_address" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="@string/login_mobidziennik_help_login"
android:textSize="18sp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/login_mobidziennik_help_login"
android:src="@drawable/login_help_mobidziennik_login" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/imageView2"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-login"
app:iiv_size="32dp"
tools:srcCompat="@android:drawable/stat_sys_phone_call_forward" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:textSize="24sp"
android:text="@string/login_progress_title" />
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="8dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="8dp"
android:indeterminate="true" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-check"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_summary_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_summary_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/profileListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:clipToPadding="false"
tools:listitem="@layout/row_login_profile_list_item"
tools:itemCount="10">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/registerMeSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:checked="true"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:text="@string/login_allow_registration" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/anotherButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/login_summary_add_student"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/finishButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:text="@string/done"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,99 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-sync"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_sync_title"
android:textSize="24sp" />
<ProgressBar
android:id="@+id/loginSyncProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="8dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="8dp"
android:max="100"
android:indeterminate="false" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/loginSyncSubtitle1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
tools:text="@string/login_sync_subtitle_1_format" />
<TextView
android:id="@+id/loginSyncSubtitle2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
tools:text="@string/login_sync_subtitle_2_format" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,169 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginEmailLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_email"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_password"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,213 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd_account_circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_vulcan_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_vulcan_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginTokenLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:hint="@string/login_hint_token"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
android:layout_marginStart="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginToken"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/loginQrScan"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:padding="4dp"
android:layout_marginRight="16dp"
android:layout_marginLeft="8dp"
android:scaleType="centerInside"
tools:srcCompat="@tools:sample/avatars"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/login_vulcan_qr" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginSymbolLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_symbol"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginSymbol"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPinLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_pin"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-help-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_vulcan_help_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_vulcan_help_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="@string/login_vulcan_help_register"
android:textSize="18sp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/login_vulcan_help_register"
android:src="@drawable/login_help_vulcan_register" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="@string/login_vulcan_help_token"
android:textSize="18sp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:adjustViewBounds="true"
android:contentDescription="@string/login_vulcan_help_token"
android:src="@drawable/login_help_vulcan_token" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-9.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
@ -8,11 +12,6 @@
android:orientation="vertical"
android:visibility="visible">
<Space
android:layout_width="0dp"
android:layout_height="32dp"
android:visibility="gone" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
@ -26,23 +25,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_login" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/snackbarAnchor"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_gravity="bottom"
android:text="@string/done"
android:visibility="invisible"/>
android:layout_marginBottom="16dp"
android:visibility="invisible" />
</FrameLayout>
</layout>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-9.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/topLogo"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-school"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="16dp"
android:text="@string/login_chooser_title"
android:textSize="24sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="2dp"
android:text="@string/login_chooser_subtitle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_weight="1"
tools:itemCount="5"
tools:listitem="@layout/login_chooser_item" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cancelButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/cancel"
android:textAllCaps="false" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/help"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-10.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="12dp">
<ImageView
android:id="@+id/logo"
android:layout_width="100dp"
android:layout_height="60dp"
android:adjustViewBounds="true"
tools:src="@drawable/login_logo_vulcan" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="8dp"
android:background="@drawable/divider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="Vulcan UONET+" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="Dziennik Uczniowie Optivum NET+" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-10.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="12dp">
<ImageView
android:id="@+id/logo"
android:layout_width="36dp"
android:layout_height="36dp"
android:adjustViewBounds="true"
tools:src="@drawable/login_mode_librus_email" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="8dp"
android:background="@drawable/divider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="Zaloguj używając e-maila" />
<TextView
android:id="@+id/hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
tools:text="(zalecane)" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="Musisz posiadać konto Librus Rodzina" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-14.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -9,71 +14,49 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:paddingHorizontal="24dp">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
android:layout_marginTop="32dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-cellphone-arrow-down"
app:iiv_icon="cmd-flag-checkered"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
tools:srcCompat="@android:drawable/stat_sys_phone_call_forward" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_migration_title"
android:text="@string/login_finish_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/subTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_migration_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
tools:text="@string/login_finish_subtitle" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="end"
android:orientation="horizontal">
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/doneButton"
style="@style/Widget.MaterialComponents.Button"
android:id="@+id/finishButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_marginHorizontal="16dp"
android:text="@string/done"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-11.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:id="@+id/formContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="32dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="24sp"
tools:text="Zaloguj się - Vulcan UONET+" />
<TextView
android:id="@+id/subTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="Opolska eSzkoła" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="4dp"
android:background="@drawable/divider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:text="Podaj dane logowania, jakich używasz podczas logowania na stronie internetowej swojego e-dziennika." />
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?selectableItemBackgroundBorderless"
app:iiv_color="?colorOnBackground"
app:iiv_icon="cmd-help-circle-outline"
app:iiv_size="24dp"
tools:src="@android:drawable/ic_menu_help" />
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/fakeLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fake login" />
<LinearLayout
android:id="@+id/errorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:background="@drawable/bg_rounded_8dp"
android:gravity="center_vertical"
android:minHeight="40dp"
android:orientation="horizontal"
android:paddingHorizontal="8dp"
android:visibility="gone"
tools:backgroundTint="?colorError"
tools:visibility="visible">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="24dp"
android:layout_height="24dp"
app:iiv_color="?colorOnError"
app:iiv_icon="cmd-alert-circle-outline"
app:iiv_size="20dp" />
<TextView
android:id="@+id/errorText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="4dp"
android:layout_weight="1"
android:textColor="?colorOnError"
android:textStyle="bold"
tools:text="Nieprawidłowy login lub hasło, konto nie zostało aktywowane, lub dziennik ma problemy egzystencjalne" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/back"
android:textAllCaps="false" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-11.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
tools:hint="Hint"
tools:startIconDrawable="@android:drawable/ic_menu">
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/textEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
</layout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-11.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
tools:hint="Hint">
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/textEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/qrButton"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/transparent"
android:padding="4dp"
android:scaleType="centerInside"
tools:srcCompat="@tools:sample/avatars" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-10.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="12dp">
<ImageView
android:id="@+id/logo"
android:layout_width="100dp"
android:layout_height="60dp"
android:adjustViewBounds="true"
tools:src="@sample/vulcan" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="8dp"
android:background="@drawable/divider" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Medium"
tools:text="Lubelski Portal Oświatowy" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="edu.lublin.eu" />
</LinearLayout>
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/screenshotButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="?selectableItemBackgroundBorderless"
app:iiv_color="?colorOnBackground"
app:iiv_icon="cmd-image-search-outline"
app:iiv_size="36dp" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2020-4-10.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/topLogo"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-tooltip-account"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="16dp"
android:text="@string/login_platform_list_title"
android:textSize="24sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="2dp"
android:text="@string/login_platform_list_subtitle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_weight="1"
android:visibility="gone"
tools:itemCount="5"
tools:listitem="@layout/login_platform_item" />
<LinearLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="16dp">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/login_platform_list_loading"
android:textAppearance="@style/NavView.TextView.Medium" />
<TextView
android:id="@+id/timeoutText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/login_platform_list_loading_timeout"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/back"
android:textAllCaps="false" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/help"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</layout>

Some files were not shown because too many files have changed in this diff Show More