From 97dc8d12f1b4286b2c7a93a81b51ef0433a1f9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 16 Apr 2020 11:01:53 +0200 Subject: [PATCH] [Login] Add new login user interface. --- app/sampledata/vulcan/edu.lublin.eu.png | Bin 0 -> 7770 bytes app/src/main/AndroidManifest.xml | 3 - .../java/pl/szczodrzynski/edziennik/App.kt | 4 + .../pl/szczodrzynski/edziennik/Extensions.kt | 3 +- .../data/api/edziennik/librus/DataLibrus.kt | 10 +- .../librus/firstlogin/LibrusFirstLogin.kt | 3 +- .../librus/login/LibrusLoginPortal.kt | 2 + .../edziennik/data/api/models/Data.kt | 7 + .../edziennik/data/api/szkolny/SzkolnyApi.kt | 14 +- .../data/api/szkolny/SzkolnyService.kt | 9 +- .../edziennik/data/db/entity/Profile.kt | 2 +- .../ui/modules/login/LoginActivity.kt | 19 +- .../ui/modules/login/LoginChooserAdapter.kt | 135 +++++++ .../ui/modules/login/LoginChooserFragment.kt | 137 +++---- .../modules/login/LoginEdudziennikFragment.kt | 121 ------ .../ui/modules/login/LoginFinishFragment.kt | 18 +- .../ui/modules/login/LoginFormFragment.kt | 177 +++++++++ .../edziennik/ui/modules/login/LoginInfo.kt | 358 ++++++++++++++++++ .../modules/login/LoginIuczniowieFragment.kt | 109 ------ .../login/LoginIuczniowieHelpFragment.java | 48 --- .../login/LoginLibrusCaptchaActivity.kt | 116 ------ .../ui/modules/login/LoginLibrusFragment.kt | 97 ----- .../login/LoginLibrusHelpFragment.java | 48 --- .../modules/login/LoginLibrusJstFragment.kt | 122 ------ .../login/LoginMobidziennikFragment.kt | 114 ------ .../login/LoginMobidziennikHelpFragment.java | 48 --- .../ui/modules/login/LoginPlatformAdapter.kt | 50 +++ .../login/LoginPlatformListFragment.kt | 100 +++++ .../ui/modules/login/LoginProfileObject.java | 29 -- .../ui/modules/login/LoginProgressFragment.kt | 41 +- .../ui/modules/login/LoginSummaryAdapter.kt | 80 ++++ .../ui/modules/login/LoginSummaryFragment.kt | 27 +- .../login/LoginSummaryProfileAdapter.kt | 81 ---- .../modules/login/LoginSyncErrorFragment.kt | 14 +- .../ui/modules/login/LoginSyncFragment.kt | 23 +- .../ui/modules/login/LoginTemplateFragment.kt | 97 ----- .../ui/modules/login/LoginVulcanFragment.kt | 144 ------- .../login/LoginVulcanHelpFragment.java | 48 --- .../login/viewholder/ModeViewHolder.kt | 39 ++ .../login/viewholder/PlatformViewHolder.kt | 35 ++ .../login/viewholder/RegisterViewHolder.kt | 32 ++ .../modules/settings/SettingsNewFragment.java | 45 ++- ...librus.png => login_mode_librus_email.png} | Bin .../res/drawable/login_mode_librus_jst.png | Bin 0 -> 771 bytes ...gia.png => login_mode_librus_synergia.png} | Bin .../drawable/login_mode_mobidziennik_web.png | Bin 0 -> 6008 bytes ...enniczek.png => login_mode_vulcan_api.png} | Bin ...o_vulcan.png => login_mode_vulcan_web.png} | Bin .../main/res/drawable/logo_mobidziennik.png | Bin 2567 -> 0 bytes .../res/layout/activity_grades_editor.xml | 194 ---------- app/src/main/res/layout/activity_main.xml | 87 ----- app/src/main/res/layout/activity_szkolny.xml | 2 +- .../res/layout/activity_web_push_config.xml | 178 --------- .../res/layout/fragment_login_chooser.xml | 262 ------------- .../res/layout/fragment_login_edudziennik.xml | 174 --------- .../main/res/layout/fragment_login_finish.xml | 92 ----- .../res/layout/fragment_login_iuczniowie.xml | 189 --------- .../layout/fragment_login_iuczniowie_help.xml | 97 ----- .../main/res/layout/fragment_login_librus.xml | 169 --------- .../res/layout/fragment_login_librus_help.xml | 97 ----- .../res/layout/fragment_login_librus_jst.xml | 194 ---------- .../layout/fragment_login_migration_sync.xml | 98 ----- .../layout/fragment_login_mobidziennik.xml | 191 ---------- .../fragment_login_mobidziennik_help.xml | 129 ------- .../res/layout/fragment_login_progress.xml | 48 --- .../res/layout/fragment_login_summary.xml | 124 ------ .../main/res/layout/fragment_login_sync.xml | 99 ----- .../res/layout/fragment_login_template.xml | 169 --------- .../main/res/layout/fragment_login_vulcan.xml | 213 ----------- .../res/layout/fragment_login_vulcan_help.xml | 129 ------- ...{activity_login.xml => login_activity.xml} | 23 +- .../res/layout/login_chooser_fragment.xml | 79 ++++ .../main/res/layout/login_chooser_item.xml | 52 +++ .../res/layout/login_chooser_mode_item.xml | 62 +++ ...igration.xml => login_finish_fragment.xml} | 61 ++- .../main/res/layout/login_form_fragment.xml | 152 ++++++++ app/src/main/res/layout/login_form_item.xml | 26 ++ .../main/res/layout/login_form_item_qr.xml | 44 +++ .../main/res/layout/login_platform_item.xml | 65 ++++ .../layout/login_platform_list_fragment.xml | 111 ++++++ .../res/layout/login_progress_fragment.xml | 40 ++ .../res/layout/login_summary_fragment.xml | 100 +++++ .../main/res/layout/login_summary_item.xml | 78 ++++ ...rror.xml => login_sync_error_fragment.xml} | 58 +-- .../main/res/layout/login_sync_fragment.xml | 55 +++ .../layout/row_login_profile_list_item.xml | 4 +- app/src/main/res/navigation/nav_login.xml | 154 ++------ app/src/main/res/values-en/strings.xml | 4 +- app/src/main/res/values/strings.xml | 34 +- 89 files changed, 2196 insertions(+), 4550 deletions(-) create mode 100644 app/sampledata/vulcan/edu.lublin.eu.png create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt rename app/src/main/res/drawable/{logo_librus.png => login_mode_librus_email.png} (100%) create mode 100644 app/src/main/res/drawable/login_mode_librus_jst.png rename app/src/main/res/drawable/{logo_synergia.png => login_mode_librus_synergia.png} (100%) create mode 100644 app/src/main/res/drawable/login_mode_mobidziennik_web.png rename app/src/main/res/drawable/{logo_dzienniczek.png => login_mode_vulcan_api.png} (100%) rename app/src/main/res/drawable/{logo_vulcan.png => login_mode_vulcan_web.png} (100%) delete mode 100644 app/src/main/res/drawable/logo_mobidziennik.png delete mode 100644 app/src/main/res/layout/activity_grades_editor.xml delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/activity_web_push_config.xml delete mode 100644 app/src/main/res/layout/fragment_login_chooser.xml delete mode 100644 app/src/main/res/layout/fragment_login_edudziennik.xml delete mode 100644 app/src/main/res/layout/fragment_login_finish.xml delete mode 100644 app/src/main/res/layout/fragment_login_iuczniowie.xml delete mode 100644 app/src/main/res/layout/fragment_login_iuczniowie_help.xml delete mode 100644 app/src/main/res/layout/fragment_login_librus.xml delete mode 100644 app/src/main/res/layout/fragment_login_librus_help.xml delete mode 100644 app/src/main/res/layout/fragment_login_librus_jst.xml delete mode 100644 app/src/main/res/layout/fragment_login_migration_sync.xml delete mode 100644 app/src/main/res/layout/fragment_login_mobidziennik.xml delete mode 100644 app/src/main/res/layout/fragment_login_mobidziennik_help.xml delete mode 100644 app/src/main/res/layout/fragment_login_progress.xml delete mode 100644 app/src/main/res/layout/fragment_login_summary.xml delete mode 100644 app/src/main/res/layout/fragment_login_sync.xml delete mode 100644 app/src/main/res/layout/fragment_login_template.xml delete mode 100644 app/src/main/res/layout/fragment_login_vulcan.xml delete mode 100644 app/src/main/res/layout/fragment_login_vulcan_help.xml rename app/src/main/res/layout/{activity_login.xml => login_activity.xml} (71%) create mode 100644 app/src/main/res/layout/login_chooser_fragment.xml create mode 100644 app/src/main/res/layout/login_chooser_item.xml create mode 100644 app/src/main/res/layout/login_chooser_mode_item.xml rename app/src/main/res/layout/{fragment_login_migration.xml => login_finish_fragment.xml} (51%) create mode 100644 app/src/main/res/layout/login_form_fragment.xml create mode 100644 app/src/main/res/layout/login_form_item.xml create mode 100644 app/src/main/res/layout/login_form_item_qr.xml create mode 100644 app/src/main/res/layout/login_platform_item.xml create mode 100644 app/src/main/res/layout/login_platform_list_fragment.xml create mode 100644 app/src/main/res/layout/login_progress_fragment.xml create mode 100644 app/src/main/res/layout/login_summary_fragment.xml create mode 100644 app/src/main/res/layout/login_summary_item.xml rename app/src/main/res/layout/{fragment_login_sync_error.xml => login_sync_error_fragment.xml} (56%) create mode 100644 app/src/main/res/layout/login_sync_fragment.xml diff --git a/app/sampledata/vulcan/edu.lublin.eu.png b/app/sampledata/vulcan/edu.lublin.eu.png new file mode 100644 index 0000000000000000000000000000000000000000..ece8b925896242a7f232a6b683bbec847d9fa24a GIT binary patch literal 7770 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 g(n(28{Xy#c|HwvM^5FDHPyhe`07*qoM6N<$f}d - 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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt index 166a4ae5..5198d155 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt @@ -146,12 +146,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { } val error = if (response.code() == 200) null else json.getJsonArray("errors")?.getString(0) + ?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString error?.let { code -> when { code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN // this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL + code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR }.let { errorCode -> data.error(ApiError(TAG, errorCode) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index 5e998425..ed1fba00 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -197,6 +197,13 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt profile.userCode = generateUserCode() + // update profile subname with class name, school year and account type + profile.subname = joinNotNullStrings( + " - ", + profile.studentClassName, + "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart + 1}" + ) + " " + app.getString(if (profile.isParent) R.string.account_type_parent else R.string.account_type_child) + db.profileDao().add(profile) db.loginStoreDao().add(loginStore) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt index bdd14f33..ae82ff8f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt @@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time import retrofit2.Response @@ -73,7 +74,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { suspend inline fun runCatching(errorSnackbar: ErrorSnackbar, crossinline block: SzkolnyApi.() -> T?): T? { return try { - withContext(Dispatchers.Default) { block() } + withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) } } catch (e: Exception) { errorSnackbar.addError(e.toApiError(TAG)).show() @@ -82,7 +83,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { } suspend inline fun runCatching(activity: AppCompatActivity, crossinline block: SzkolnyApi.() -> T?): T? { return try { - withContext(Dispatchers.Default) { block() } + withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) } } catch (e: Exception) { ErrorDetailsDialog( @@ -95,7 +96,7 @@ class SzkolnyApi(val app: App) : CoroutineScope { } inline fun runCatching(block: SzkolnyApi.() -> T, onError: (e: Throwable) -> Unit): T? { return try { - block() + block.invoke(this@SzkolnyApi) } catch (e: Exception) { onError(e) @@ -327,4 +328,11 @@ class SzkolnyApi(val app: App) : CoroutineScope { return parseResponse(response).message } + + @Throws(Exception::class) + fun getPlatforms(registerName: String): List { + val response = api.appLoginPlatforms(registerName).execute() + + return parseResponse(response) + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt index 0e3dd574..bce3aef0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt @@ -6,11 +6,9 @@ package pl.szczodrzynski.edziennik.data.api.szkolny import pl.szczodrzynski.edziennik.data.api.szkolny.request.* import pl.szczodrzynski.edziennik.data.api.szkolny.response.* +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo import retrofit2.Call -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Query +import retrofit2.http.* interface SzkolnyService { @@ -34,4 +32,7 @@ interface SzkolnyService { @POST("feedbackMessage") fun feedbackMessage(@Body request: FeedbackMessageRequest): Call> + + @GET("appLogin/platforms/{registerName}") + fun appLoginPlatforms(@Path("registerName") registerName: String): Call>> } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt index 1442f40c..aa28faf7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Profile.kt @@ -80,7 +80,7 @@ open class Profile( var dateYearEnd = Date(studentSchoolYearStart + 1, 6, 30) fun getSemesterStart(semester: Int) = if (semester == 1) dateSemester1Start else dateSemester2Start fun getSemesterEnd(semester: Int) = if (semester == 1) dateSemester2Start.clone().stepForward(0, 0, -1) else dateYearEnd - fun dateToSemester(date: Date) = if (date.value >= getSemesterStart(2).value) 2 else 1 + fun dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1 @delegate:Ignore val currentSemester by lazy { dateToSemester(Date.getToday()) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt index 7f65279f..c686b857 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginActivity.kt @@ -1,3 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + package pl.szczodrzynski.edziennik.ui.modules.login import android.app.Activity @@ -14,20 +18,19 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore -import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding +import pl.szczodrzynski.edziennik.databinding.LoginActivityBinding import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar import kotlin.coroutines.CoroutineContext class LoginActivity : AppCompatActivity(), CoroutineScope { companion object { private const val TAG = "LoginActivity" - @JvmField - var navOptions: NavOptions? = null var thisOneIsTricky = 0 } private val app: App by lazy { applicationContext as App } - private lateinit var b: ActivityLoginBinding + private lateinit var b: LoginActivityBinding + lateinit var navOptions: NavOptions val nav by lazy { Navigation.findNavController(this, R.id.nav_host_fragment) } val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } @@ -36,7 +39,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { get() = job + Dispatchers.Main var lastError: ApiError? = null - val profiles = mutableListOf() + val profiles = mutableListOf() val loginStores = mutableListOf() override fun onBackPressed() { @@ -50,7 +53,9 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { return if (destination.id == R.id.loginSyncFragment) return - if (destination.id == R.id.loginChooserFragment) { + if (destination.id == R.id.loginFinishFragment) + return + if (destination.id == R.id.loginChooserFragment && loginStores.isEmpty()) { setResult(Activity.RESULT_CANCELED) finish() return @@ -83,7 +88,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope { .setPopExitAnim(R.anim.slide_out_right) .build() - b = ActivityLoginBinding.inflate(layoutInflater) + b = LoginActivityBinding.inflate(layoutInflater) setContentView(b.root) errorSnackbar.setCoordinator(b.coordinator, b.snackbarAnchor) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt new file mode 100644 index 00000000..c9ed2d6e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserAdapter.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.ModeViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.RegisterViewHolder +import kotlin.coroutines.CoroutineContext + +class LoginChooserAdapter( + val activity: AppCompatActivity, + val onModeClick: ((loginType: LoginInfo.Register, loginMode: LoginInfo.Mode) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "LoginChooserAdapter" + private const val ITEM_TYPE_REGISTER = 0 + private const val ITEM_TYPE_MODE = 1 + const val STATE_CLOSED = 0 + const val STATE_OPENED = 1 + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + ITEM_TYPE_REGISTER -> RegisterViewHolder(inflater, parent) + ITEM_TYPE_MODE -> ModeViewHolder(inflater, parent) + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is LoginInfo.Register -> ITEM_TYPE_REGISTER + is LoginInfo.Mode -> ITEM_TYPE_MODE + else -> throw IllegalArgumentException("Incorrect viewType") + } + } + + private val onClickListener = View.OnClickListener { view -> + val model = view.getTag(R.string.tag_key_model) + if (model is LoginInfo.Register && model.loginModes.size == 1) { + onModeClick?.invoke(model, model.loginModes.first()) + return@OnClickListener + } + if (model is LoginInfo.Mode) { + val loginInfo = items.firstOrNull { + it is LoginInfo.Register && it.loginModes.contains(model) + } as? LoginInfo.Register + ?: return@OnClickListener + + onModeClick?.invoke(loginInfo, model) + return@OnClickListener + } + if (model !is LoginInfo.Register) + return@OnClickListener + expandModel(model, view) + } + + private fun expandModel(model: LoginInfo.Register, view: View?, notifyAdapter: Boolean = true) { + val position = items.indexOf(model) + if (position == -1) + return + + if (model.state == STATE_CLOSED) { + + val subItems = model.items + + model.state = STATE_OPENED + items.addAll(position + 1, subItems) + if (notifyAdapter) notifyItemRangeInserted(position + 1, subItems.size) + } + else { + val start = position + 1 + var end: Int = items.size + for (i in start until items.size) { + val model1 = items[i] + val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + if (level <= model.level) { + end = i + break + } else { + if (model1 is ExpandableItemModel<*> && model1.state == STATE_OPENED) { + model1.state = STATE_CLOSED + } + } + } + + if (end != -1) { + items.subList(start, end).clear() + if (notifyAdapter) notifyItemRangeRemoved(start, end - start) + } + + model.state = STATE_CLOSED + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (holder !is BindableViewHolder<*, *>) + return + + holder.itemView.setTag(R.string.tag_key_model, item) + + when { + holder is RegisterViewHolder && item is LoginInfo.Register -> holder.onBind(activity, app, item, position, this) + holder is ModeViewHolder && item is LoginInfo.Mode -> holder.onBind(activity, app, item, position, this) + } + + holder.itemView.setOnClickListener(onClickListener) + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt index a522d427..37391924 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginChooserFragment.kt @@ -1,78 +1,90 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + package pl.szczodrzynski.edziennik.ui.modules.login import android.app.Activity -import android.content.Intent import android.os.Bundle -import android.os.Process import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.animation.AnimationUtils -import android.widget.CompoundButton +import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import com.afollestad.materialdialogs.DialogAction -import com.afollestad.materialdialogs.MaterialDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.Bundle import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.databinding.FragmentLoginChooserBinding -import pl.szczodrzynski.edziennik.onChange +import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding import pl.szczodrzynski.edziennik.onClick -import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity -import pl.szczodrzynski.edziennik.utils.Anim -import kotlin.system.exitProcess +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext - -class LoginChooserFragment : Fragment() { +class LoginChooserFragment : Fragment(), CoroutineScope { companion object { private const val TAG = "LoginChooserFragment" - var fakeLogin = false } private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginChooserBinding + private lateinit var b: LoginChooserFragmentBinding private val nav by lazy { activity.nav } + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginChooserBinding.inflate(inflater) + b = LoginChooserFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - b.topLogo.onClick { - if (LoginActivity.thisOneIsTricky <= -1) { - LoginActivity.thisOneIsTricky = 999 - } - if (LoginActivity.thisOneIsTricky in 0..7) { - LoginActivity.thisOneIsTricky++ - if (LoginActivity.thisOneIsTricky == 7) { - b.topLogo.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.shake)); - if (b.devMode.visibility != View.VISIBLE) - Anim.expand(b.devMode, 500, null); - LoginActivity.thisOneIsTricky = 3 - } + if (!isAdded) return + + val adapter = LoginChooserAdapter(activity) { loginType, loginMode -> + if (loginMode.isPlatformSelection) { + nav.navigate(R.id.loginPlatformListFragment, Bundle( + "loginType" to loginType.loginType, + "loginMode" to loginMode.loginMode + ), activity.navOptions) + return@LoginChooserAdapter } + + nav.navigate(R.id.loginFormFragment, Bundle( + "loginType" to loginType.loginType, + "loginMode" to loginMode.loginMode + ), activity.navOptions) + } + + LoginInfo.chooserList = LoginInfo.chooserList + ?: LoginInfo.list.toMutableList() + + adapter.items = LoginInfo.chooserList!! + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) } - b.loginMobidziennikLogo.onClick { nav.navigate(R.id.loginMobidziennikFragment, null, LoginActivity.navOptions) } - b.loginLibrusLogo.onClick { nav.navigate(R.id.loginLibrusFragment, null, LoginActivity.navOptions) } - b.loginVulcanLogo.onClick { nav.navigate(R.id.loginVulcanFragment, null, LoginActivity.navOptions) } - b.loginIuczniowieLogo.onClick { nav.navigate(R.id.loginIuczniowieFragment, null, LoginActivity.navOptions) } - b.loginLibrusJstLogo.onClick { nav.navigate(R.id.loginLibrusJstFragment, null, LoginActivity.navOptions) } - b.loginEdudziennikLogo.onClick { nav.navigate(R.id.loginEdudziennikFragment, null, LoginActivity.navOptions) } when { activity.loginStores.isNotEmpty() -> { // we are navigated here from LoginSummary - b.cancelButton.visibility = View.VISIBLE + b.cancelButton.isVisible = true b.cancelButton.onClick { nav.navigateUp() } } app.config.loginFinished -> { // we are navigated here from AppDrawer - b.cancelButton.visibility = View.VISIBLE + b.cancelButton.isVisible = true b.cancelButton.onClick { activity.setResult(Activity.RESULT_CANCELED) activity.finish() @@ -80,57 +92,8 @@ class LoginChooserFragment : Fragment() { } else -> { // there are no profiles - b.cancelButton.visibility = View.GONE + b.cancelButton.isVisible = false } } - - b.devMode.visibility = if (App.debugMode) View.VISIBLE else View.GONE - b.devMode.isChecked = app.config.debugMode - b.devMode.onChange { v, isChecked -> - if (isChecked) { - MaterialDialog.Builder(activity) - .title(R.string.are_you_sure) - .content(R.string.dev_mode_enable_warning) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - app.config.debugMode = true - App.devMode = true - MaterialAlertDialogBuilder(activity) - .setTitle("Restart") - .setMessage("Wymagany restart aplikacji") - .setPositiveButton("OK") { _, _ -> - Process.killProcess(Process.myPid()) - Runtime.getRuntime().exit(0) - exitProcess(0) - } - .setCancelable(false) - .show() - } - .onNegative { _: MaterialDialog?, _: DialogAction? -> - app.config.debugMode = false - App.devMode = false - b.devMode.isChecked = app.config.debugMode - b.devMode.jumpDrawablesToCurrentState() - Anim.collapse(b.devMode, 1000, null) - } - .show() - } else { - app.config.debugMode = false - App.devMode = false - /*if (b.devModeLayout.getVisibility() === View.VISIBLE) { - Anim.collapse(b.devModeTitle, 500, null) - Anim.collapse(b.devModeLayout, 500, null) - }*/ - } - } - - b.fakeLogin.visibility = if (App.devMode) View.VISIBLE else View.GONE - b.fakeLogin.isChecked = fakeLogin - b.fakeLogin.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - fakeLogin = isChecked - } - - b.helpButton.onClick { startActivity(Intent(activity, FeedbackActivity::class.java)) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt deleted file mode 100644 index be1a83a7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginEdudziennikFragment.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.animation.ArgbEvaluator -import android.animation.ObjectAnimator -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK -import pl.szczodrzynski.edziennik.databinding.FragmentLoginEdudziennikBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - - -class LoginEdudziennikFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginEdudziennikFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginEdudziennikBinding - private val nav by lazy { activity.nav } - private var hehe = 0 - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginEdudziennikBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - } - } - } - - b.topText.onClick { - if (LoginActivity.thisOneIsTricky != -1) - return@onClick - hehe++ - if (hehe >= 5) { - LoginActivity.thisOneIsTricky = 3 - val colorAnim = ObjectAnimator.ofInt( - b.topText, - "textColor", - Color.BLACK, - Color.RED, - Color.BLACK, - Color.RED, - Color.BLACK, - Color.RED, - Color.BLACK - ) - colorAnim.setEvaluator(ArgbEvaluator()) - colorAnim.duration = 1500L - colorAnim.start() - } - } - - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginEmailLayout.error = null - b.loginPasswordLayout.error = null - - val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (email.isBlank()) { - b.loginEmailLayout.error = getString(R.string.login_error_no_email) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginEmail.setText(email) - if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) { - b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_EDUDZIENNIK, - "email" to email, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt index 8f52b41a..7b2e66b8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFinishFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-4. + * Copyright (c) Kuba Szczodrzyński 2020-4-16. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -14,7 +14,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginFinishBinding +import pl.szczodrzynski.edziennik.databinding.LoginFinishFragmentBinding import kotlin.coroutines.CoroutineContext class LoginFinishFragment : Fragment(), CoroutineScope { @@ -24,27 +24,29 @@ class LoginFinishFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginFinishBinding + private lateinit var b: LoginFinishFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginFinishBinding.inflate(inflater) + b = LoginFinishFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val firstRun = !App.config.loginFinished - App.config.loginFinished = true + val firstRun = !app.config.loginFinished + app.config.loginFinished = true if (!firstRun) { - b.loginFinishSubtitle.setText(R.string.login_finish_subtitle_not_first_run) + b.subTitle.setText(R.string.login_finish_subtitle_not_first_run) } b.finishButton.onClick { @@ -74,4 +76,4 @@ class LoginFinishFragment : Fragment(), CoroutineScope { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt new file mode 100644 index 00000000..d0676105 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt @@ -0,0 +1,177 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.InputType +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import com.google.android.material.textfield.TextInputLayout +import com.google.gson.JsonParser +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.paddingDp +import com.mikepenz.iconics.utils.sizeDp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding +import pl.szczodrzynski.edziennik.databinding.LoginFormItemBinding +import pl.szczodrzynski.navlib.colorAttr +import java.util.* +import kotlin.coroutines.CoroutineContext + +class LoginFormFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "LoginFormFragment" + } + + private lateinit var app: App + private lateinit var activity: LoginActivity + private lateinit var b: LoginFormFragmentBinding + private val nav by lazy { activity.nav } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as LoginActivity?) ?: return null + context ?: return null + app = activity.application as App + b = LoginFormFragmentBinding.inflate(inflater) + return b.root + } + + @SuppressLint("ResourceType") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + b.backButton.onClick { nav.navigateUp() } + + b.errorLayout.isVisible = false + b.errorLayout.background?.setTintColor(R.attr.colorError.resolveAttr(activity)) + + val loginType = arguments?.getInt("loginType") ?: return + val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return + val loginMode = arguments?.getInt("loginMode") ?: return + val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + + val platformName = arguments?.getString("platformName") + val platformGuideText = arguments?.getString("platformGuideText") + val platformDescription = arguments?.getString("platformDescription") + val platformFormFields = arguments?.getString("platformFormFields")?.split(";") + val platformApiData = arguments?.getString("platformApiData")?.let { JsonParser().parse(it)?.asJsonObject } + + b.title.setText(R.string.login_form_title_format, app.getString(register.registerName)) + b.subTitle.text = platformName ?: app.getString(mode.name) + b.text.text = platformGuideText ?: app.getString(mode.guideText) + + val credentials = mutableMapOf() + + for (credential in mode.credentials) { + if (platformFormFields?.contains(credential.keyName) == false) + continue + + val b = LoginFormItemBinding.inflate(layoutInflater) + b.textLayout.hint = app.getString(credential.name) + if (credential.hideText) { + b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD + b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE + } + b.textEdit.addTextChangedListener { + b.textLayout.error = null + } + + b.textEdit.id = credential.name + + b.textEdit.setText(arguments?.getString(credential.keyName) ?: "") + b.textLayout.startIconDrawable = IconicsDrawable(activity) + .icon(credential.icon) + .sizeDp(24) + .paddingDp(2) + .colorAttr(activity, R.attr.colorOnBackground) + + this.b.formContainer.addView(b.root) + credentials[credential] = b + } + + activity.lastError?.let { error -> + activity.lastError = null + startCoroutineTimer(delayMillis = 200L) { + for (credential in credentials) { + credential.key.errorCodes[error.errorCode]?.let { + credential.value.textLayout.error = app.getString(it) + return@startCoroutineTimer + } + } + mode.errorCodes[error.errorCode]?.let { + b.errorText.text = app.getString(it) + b.errorLayout.isVisible = true + return@startCoroutineTimer + } + } + } + + b.loginButton.onClick { + val payload = Bundle( + "loginType" to loginType, + "loginMode" to loginMode + ) + + if (App.devMode && b.fakeLogin.isChecked) { + payload.putBoolean("fakeLogin", true) + } + + platformApiData?.entrySet()?.forEach { + payload.putString(it.key, it.value.asString) + } + + var hasErrors = false + credentials.forEach { (credential, b) -> + var text = b.textEdit.text?.toString() ?: return@forEach + if (!credential.hideText) + text = text.trim() + + if (credential.caseMode == LoginInfo.Credential.CaseMode.UPPER_CASE) + text = text.toUpperCase(Locale.getDefault()) + if (credential.caseMode == LoginInfo.Credential.CaseMode.LOWER_CASE) + text = text.toLowerCase(Locale.getDefault()) + + credential.stripTextRegex?.let { + text = text.replace(it.toRegex(), "") + } + + b.textEdit.setText(text) + + if (credential.isRequired && text.isBlank()) { + b.textLayout.error = app.getString(credential.emptyText) + hasErrors = true + return@forEach + } + + if (!text.matches(credential.validationRegex.toRegex())) { + b.textLayout.error = app.getString(credential.invalidText) + hasErrors = true + return@forEach + } + + payload.putString(credential.keyName, text) + arguments?.putString(credential.keyName, text) + } + + if (hasErrors) + return@onClick + + nav.navigate(R.id.loginProgressFragment, payload, activity.navOptions) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt new file mode 100644 index 00000000..29624d40 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt @@ -0,0 +1,358 @@ +/* + * 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 = "deviceSymbol", + 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 + ), + Credential( + keyName = "webPassword", + 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 + ) + ), + 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, + + )*/ + ) } + + data class Register( + val loginType: Int, + val internalName: String, + val registerName: Int, + @DrawableRes + val registerLogo: Int, + + val loginModes: List + ) : ExpandableItemModel(loginModes.toMutableList()) { + override var level = 1 + } + + data class Mode( + val loginMode: Int, + + @StringRes + val name: Int, + @DrawableRes + val icon: Int, + @StringRes + val hintText: Int? = null, + @StringRes + val guideText: Int, + + val isRecommended: Boolean = false, + val isTesting: Boolean = false, + val isPlatformSelection: Boolean = false, + + val credentials: List, + val errorCodes: Map + ) + + data class Platform( + val id: Int, + val loginType: Int, + val loginMode: Int, + val name: String, + val description: String?, + val guideText: String?, + val icon: String, + val screenshot: String?, + val formFields: List, + val apiData: JsonObject + ) + + data class Credential( + val keyName: String, + + @StringRes + val name: Int, + val icon: IIcon, + @StringRes + val placeholder: Int? = null, + @StringRes + val emptyText: Int, + @StringRes + val invalidText: Int, + val errorCodes: Map, + @StringRes + val hintText: Int? = null, + + val isRequired: Boolean = true, + val validationRegex: String, + val caseMode: CaseMode = CaseMode.UNCHANGED, + val hideText: Boolean = false, + val stripTextRegex: String? = null + ) { + enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE } + } + + var chooserList: MutableList? = null + var platformList: MutableMap> = mutableMapOf() +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt deleted file mode 100644 index 379eee1a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieFragment.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_IDZIENNIK -import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginIuczniowieFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginIuczniowieFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginIuczniowieBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginIuczniowieBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME -> - b.loginSchoolNameLayout.error = getString(R.string.login_error_incorrect_school_name) - ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginIuczniowieHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginSchoolNameLayout.error = null - b.loginUsernameLayout.error = null - b.loginPasswordLayout.error = null - - val schoolName = b.loginSchoolName.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val username = b.loginUsername.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (schoolName.isBlank()) { - b.loginSchoolNameLayout.error = getString(R.string.login_error_no_school_name) - errors = true - } - if (username.isBlank()) { - b.loginUsernameLayout.error = getString(R.string.login_error_no_username) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginSchoolName.setText(schoolName) - b.loginUsername.setText(username) - if (!"[a-z0-9_\\-]+".toRegex().matches(schoolName)) { - b.loginSchoolNameLayout.error = getString(R.string.login_error_incorrect_school_name) - errors = true - } - if (!"[a-z0-9_\\-]+".toRegex().matches(username)) { - b.loginUsernameLayout.error = getString(R.string.login_error_incorrect_username) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_IDZIENNIK, - "schoolName" to schoolName, - "username" to username, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java deleted file mode 100644 index 7b5c9d96..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginIuczniowieHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieHelpBinding; - -public class LoginIuczniowieHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginIuczniowieHelpBinding b; - private static final String TAG = "LoginIuczniowieHelp"; - - public LoginIuczniowieHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_iuczniowie_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt deleted file mode 100644 index 3cbf29ed..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusCaptchaActivity.kt +++ /dev/null @@ -1,116 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.annotation.SuppressLint -import android.graphics.Color -import android.os.Build -import android.os.Bundle -import android.util.Base64 -import android.webkit.JavascriptInterface -import android.webkit.WebView -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import com.afollestad.materialdialogs.MaterialDialog -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.LIBRUS_USER_AGENT -import pl.szczodrzynski.edziennik.utils.Themes -import pl.szczodrzynski.edziennik.utils.Utils.hexFromColorInt -import java.nio.charset.Charset - -class LoginLibrusCaptchaActivity : AppCompatActivity() { - companion object { - private const val TAG = "LoginLibrusCaptchaActivity" - } - - private lateinit var webView: WebView - private lateinit var dialog: AlertDialog - private lateinit var jsInterface: CaptchaCallbackInterface - - @SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setTheme(Themes.appThemeNoDisplay) - setFinishOnTouchOutside(false) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - WebView.setWebContentsDebuggingEnabled(true) - } - - val base64Content = """ -PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds -ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj -cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxjZW50ZXIgaWQ9ImdyIj48L2NlbnRlcj48YnI+PHNjcmlw -dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCdncicse3NpdGVrZXk6JzZMZjQ4 -bW9VQUFBQUFCOUNsaGR2SHI0NmdSV1ItQ04zMUNYUVBHMlUnLHRoZW1lOidUSEVNRScsY2FsbGJh -Y2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNrKGUpO30sImV4cGlyZWQtY2FsbGJhY2si -OmZ1bmN0aW9uKCl7d2luZG93LmlmLmV4cGlyZWRDYWxsYmFjayhlKTt9LCJlcnJvci1jYWxsYmFj -ayI6ZnVuY3Rpb24oKXt3aW5kb3cuaWYuZXJyb3JDYWxsYmFjayhlKTt9fSk7fTwvc2NyaXB0Pjwv -Ym9keT48L2h0bWw+""" - - val backgroundColor = if (Themes.isDark) 0x424242 else 0xffffff - val backgroundColorString = hexFromColorInt(backgroundColor) - - val htmlContent = Base64.decode(base64Content, Base64.DEFAULT) - .toString(Charset.defaultCharset()) - .replace("COLOR", backgroundColorString, true) - .replace("THEME", if (Themes.isDark) "dark" else "light") - - jsInterface = object : CaptchaCallbackInterface { - @JavascriptInterface - override fun callback(recaptchaResponse: String) { - MaterialDialog.Builder(this@LoginLibrusCaptchaActivity) - .title("Captcha checked") - .content("Response: $recaptchaResponse") - .positiveText("OK") - .show() - } - - @JavascriptInterface - override fun expiredCallback() { - MaterialDialog.Builder(this@LoginLibrusCaptchaActivity) - .title("Captcha expired") - .content("Captcha expired") - .positiveText("OK") - .show() - } - - @JavascriptInterface - override fun errorCallback() { - MaterialDialog.Builder(this@LoginLibrusCaptchaActivity) - .title("Captcha error") - .content("Captcha error") - .positiveText("OK") - .show() - } - } - - webView = WebView(this).apply { - //setBackgroundColor((backgroundColor.toLong() or 0xff000000).toInt()) - setBackgroundColor(Color.TRANSPARENT) - settings.javaScriptEnabled = true - settings.userAgentString = LIBRUS_USER_AGENT - addJavascriptInterface(jsInterface, "if") - loadDataWithBaseURL("https://portal.librus.pl/rodzina/login/", htmlContent, "text/html", "UTF-8", null) - setLayerType(WebView.LAYER_TYPE_SOFTWARE, null) - } - - dialog = MaterialAlertDialogBuilder(this) - .setTitle(R.string.login_librus_captcha_title) - .setView(webView) - .setNegativeButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - finish() - } - .setCancelable(false) - .show() - } - - interface CaptchaCallbackInterface { - @JavascriptInterface - fun callback(recaptchaResponse: String) - @JavascriptInterface - fun expiredCallback() - @JavascriptInterface - fun errorCallback() - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt deleted file mode 100644 index 11c2bdd2..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS -import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginLibrusFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginLibrusFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginLibrusBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginLibrusBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -> - b.loginEmailLayout.error = getString(R.string.login_error_account_not_activated) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginEmailLayout.error = null - b.loginPasswordLayout.error = null - - val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (email.isBlank()) { - b.loginEmailLayout.error = getString(R.string.login_error_no_email) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginEmail.setText(email) - if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) { - b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_LIBRUS, - "email" to email, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java deleted file mode 100644 index 6984498a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusHelpBinding; - -public class LoginLibrusHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginLibrusHelpBinding b; - private static final String TAG = "LoginLibrusHelp"; - - public LoginLibrusHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_librus_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt deleted file mode 100644 index d4e3d445..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginLibrusJstFragment.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.fragment.app.Fragment -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial -import com.mikepenz.iconics.utils.colorInt -import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST -import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_LIBRUS_JST -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS -import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusJstBinding -import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginLibrusJstFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginLibrusJstFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginLibrusJstBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginLibrusJstBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN, - ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST -> - b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code_or_pin) - } - } - } - - b.loginQrScan.setImageDrawable(IconicsDrawable(activity) - .icon(CommunityMaterial.Icon2.cmd_qrcode_scan) - .colorInt(Color.BLACK) - .sizeDp(72)) - b.loginQrScan.onClick { - QrScannerDialog(activity, { code -> - b.loginCode.setText(code) - if (b.loginPin.requestFocus()) { - activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - } - }) - } - - b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginCodeLayout.error = null - b.loginPinLayout.error = null - - val code = b.loginCode.text?.toString()?.toUpperCase(Locale.ROOT) ?: "" - val pin = b.loginPin.text?.toString() ?: "" - - if (code.isBlank()) { - b.loginCodeLayout.error = getString(R.string.login_error_no_code) - errors = true - } - if (pin.isBlank()) { - b.loginPinLayout.error = getString(R.string.login_error_no_pin) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginCode.setText(code) - if (!"[A-Z0-9_]+".toRegex().matches(code)) { - b.loginCodeLayout.error = getString(R.string.login_error_incorrect_code) - errors = true - } - if (!"[a-z0-9_]+".toRegex().matches(pin)) { - b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_LIBRUS, - "loginMode" to LOGIN_MODE_LIBRUS_JST, - "accountCode" to code, - "accountPin" to pin - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt deleted file mode 100644 index de0deb3d..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikFragment.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginMobidziennikFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginMobidziennikFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginMobidziennikBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginMobidziennikBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD -> - b.loginPasswordLayout.error = getString(R.string.login_error_old_password) - ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED -> - b.loginUsernameLayout.error = getString(R.string.sync_error_archived) - ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS -> - b.loginServerAddressLayout.error = getString(R.string.login_error_incorrect_address) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginMobidziennikHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginServerAddressLayout.error = null - b.loginUsernameLayout.error = null - b.loginPasswordLayout.error = null - - val serverName = b.loginServerAddress.text - ?.toString() - ?.toLowerCase(Locale.ROOT) - ?.replace("(?:http://|www.|mobidziennik\\.pl|wizja\\.net|\\.)".toRegex(), "") ?: "" - val username = b.loginUsername.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (serverName.isBlank()) { - b.loginServerAddressLayout.error = getString(R.string.login_error_no_address) - errors = true - } - if (username.isBlank()) { - b.loginUsernameLayout.error = getString(R.string.login_error_no_login) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginServerAddress.setText(serverName) - b.loginUsername.setText(username) - if (!"^[a-z0-9_\\-]+$".toRegex().matches(serverName)) { - b.loginServerAddressLayout.error = getString(R.string.login_error_incorrect_address) - errors = true - } - if (!"^[a-z0-9_\\-@+.]+$".toRegex().matches(username)) { - b.loginUsernameLayout.error = getString(R.string.login_error_incorrect_login) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_MOBIDZIENNIK, - "serverName" to serverName, - "username" to username, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java deleted file mode 100644 index 511f6682..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginMobidziennikHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikHelpBinding; - -public class LoginMobidziennikHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginMobidziennikHelpBinding b; - private static final String TAG = "LoginMobidziennikHelp"; - - public LoginMobidziennikHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_mobidziennik_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt new file mode 100644 index 00000000..5bc80f36 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformAdapter.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.ui.modules.login.viewholder.PlatformViewHolder +import kotlin.coroutines.CoroutineContext + +class LoginPlatformAdapter( + val activity: AppCompatActivity, + val onPlatformClick: ((platform: LoginInfo.Platform) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "LoginPlatformAdapter" + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlatformViewHolder { + val inflater = LayoutInflater.from(parent.context) + return PlatformViewHolder(inflater, parent) + } + + override fun onBindViewHolder(holder: PlatformViewHolder, position: Int) { + val item = items[position] + holder.onBind(activity, app, item, position, this) + onPlatformClick?.let { + holder.b.root.onClick { _ -> it(item) } + } + } + + override fun getItemCount() = items.size +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt new file mode 100644 index 00000000..9beb0860 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginPlatformListFragment.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.databinding.LoginPlatformListFragmentBinding +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext + +class LoginPlatformListFragment : Fragment(), CoroutineScope { + companion object { + private const val TAG = "LoginPlatformListFragment" + } + + private lateinit var app: App + private lateinit var activity: LoginActivity + private lateinit var b: LoginPlatformListFragmentBinding + private val nav by lazy { activity.nav } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local/private variables go here + private val api by lazy { SzkolnyApi(app) } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + activity = (getActivity() as LoginActivity?) ?: return null + context ?: return null + app = activity.application as App + b = LoginPlatformListFragmentBinding.inflate(inflater) + return b.root + } + + private lateinit var timeoutJob: Job + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + b.backButton.onClick { nav.navigateUp() } + + val loginType = arguments?.getInt("loginType") ?: return + val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return + val loginMode = arguments?.getInt("loginMode") ?: return + val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + + timeoutJob = startCoroutineTimer(5000L) { + b.timeoutText.isVisible = true + timeoutJob.cancel() + } + + val adapter = LoginPlatformAdapter(activity) { platform -> + nav.navigate(R.id.loginFormFragment, Bundle( + "loginType" to platform.loginType, + "loginMode" to platform.loginMode, + "platformName" to platform.name, + "platformDescription" to platform.description, + "platformFormFields" to platform.formFields.joinToString(";"), + "platformApiData" to platform.apiData.toString() + ), activity.navOptions) + } + + launch { + val platforms = LoginInfo.platformList[mode.name] + ?: run { + api.runCatching(activity) { + getPlatforms(register.internalName) + } ?: run { + nav.navigateUp() + return@launch + } + } + LoginInfo.platformList[mode.name] = platforms + + adapter.items = platforms + b.list.adapter = adapter + b.list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + timeoutJob.cancel() + b.loadingLayout.isVisible = false + b.list.isVisible = true + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java deleted file mode 100644 index a86cfa86..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProfileObject.java +++ /dev/null @@ -1,29 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - - -import java.util.ArrayList; -import java.util.List; - -import androidx.annotation.NonNull; -import pl.szczodrzynski.edziennik.data.db.entity.LoginStore; -import pl.szczodrzynski.edziennik.data.db.entity.Profile; - -public class LoginProfileObject { - LoginStore loginStore = null; - List profileList = new ArrayList<>(); - List selectedList = new ArrayList<>(); - - public LoginProfileObject(@NonNull LoginStore loginStore, @NonNull List profileList) { - this.loginStore = loginStore; - this.profileList = profileList; - for (Profile ignored : profileList) { - selectedList.add(true); - } - } - - public LoginProfileObject addProfile(Profile profile) { - profileList.add(profile); - selectedList.add(true); - return this; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt index 929d9551..ab76327e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. + * Copyright (c) Kuba Szczodrzyński 2020-4-16. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -27,8 +27,10 @@ import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore -import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding +import pl.szczodrzynski.edziennik.databinding.LoginProgressFragmentBinding +import pl.szczodrzynski.edziennik.joinNotNullStrings import kotlin.coroutines.CoroutineContext +import kotlin.math.max class LoginProgressFragment : Fragment(), CoroutineScope { companion object { @@ -37,22 +39,26 @@ class LoginProgressFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginProgressBinding + private lateinit var b: LoginProgressFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginProgressBinding.inflate(inflater) + b = LoginProgressFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + val args = arguments ?: run { activity.error(ApiError(TAG, LOGIN_NO_ARGUMENTS)) nav.navigateUp() @@ -66,19 +72,21 @@ 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) } } @@ -94,10 +102,21 @@ class LoginProgressFragment : Fragment(), CoroutineScope { .show() return } + + // update subnames with school years and class name + for (profile in event.profileList) { + val schoolYearName = "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart + 1}" + profile.subname = joinNotNullStrings( + " - ", + profile.studentClassName, + schoolYearName + ) + } + activity.loginStores += event.loginStore - activity.profiles += event.profileList.map { LoginSummaryProfileAdapter.Item(it) } + activity.profiles += event.profileList.map { LoginSummaryAdapter.Item(it) } activity.errorSnackbar.dismiss() - nav.navigate(R.id.loginSummaryFragment, null, LoginActivity.navOptions) + nav.navigate(R.id.loginSummaryFragment, null, activity.navOptions) } @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt new file mode 100644 index 00000000..bc9cafbf --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryAdapter.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.LoginSummaryItemBinding +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.trigger +import kotlin.coroutines.CoroutineContext + +class LoginSummaryAdapter( + val activity: LoginActivity, + val onSelectionChanged: ((item: Item) -> Unit)? = null +) : RecyclerView.Adapter(), CoroutineScope { + companion object { + private const val TAG = "LoginSummaryAdapter" + } + + private val app = activity.applicationContext as App + // optional: place the manager here + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + var items = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return ViewHolder(LoginSummaryItemBinding.inflate(inflater, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + val b = holder.b + val profile = item.profile + val loginStore = activity.loginStores.firstOrNull { it.id == profile.loginStoreId } + ?: return + + val loginType = loginStore.type + val register = LoginInfo.list.firstOrNull { it.loginType == loginType } ?: return + val loginMode = loginStore.mode + val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return + + b.profileName.text = profile.name + b.profileDetails.text = profile.subname + b.checkBox.isChecked = item.isSelected + b.modeIcon.setImageResource(mode.icon) + + if (profile.isParent) { + b.accountType.setText(R.string.account_type_parent) + } else { + b.accountType.setText(R.string.account_type_child) + } + + b.root.onClick { + b.checkBox.trigger() + } + b.checkBox.setOnCheckedChangeListener { _, isChecked -> + item.isSelected = isChecked + onSelectionChanged?.invoke(item) + } + } + + override fun getItemCount() = items.size + + class ViewHolder(val b: LoginSummaryItemBinding) : RecyclerView.ViewHolder(b.root) + + class Item(val profile: Profile, var isSelected: Boolean = true) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt index b16c0256..63525c45 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. + * Copyright (c) Kuba Szczodrzyński 2020-4-16. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -16,7 +16,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginSummaryBinding +import pl.szczodrzynski.edziennik.databinding.LoginSummaryFragmentBinding import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import kotlin.coroutines.CoroutineContext @@ -27,27 +27,32 @@ class LoginSummaryFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginSummaryBinding + private lateinit var b: LoginSummaryFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginSummaryBinding.inflate(inflater) + b = LoginSummaryFragmentBinding.inflate(inflater) return b.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - b.profileListView.apply { - adapter = LoginSummaryProfileAdapter(activity, activity.profiles) { item -> - b.finishButton.isEnabled = activity.profiles.any { it.isSelected } - } + val adapter = LoginSummaryAdapter(activity) { _ -> + b.finishButton.isEnabled = activity.profiles.any { it.isSelected } + } + + adapter.items = activity.profiles + b.list.adapter = adapter + b.list.apply { isNestedScrollingEnabled = false setHasFixedSize(true) layoutManager = LinearLayoutManager(context) @@ -66,7 +71,7 @@ class LoginSummaryFragment : Fragment(), CoroutineScope { } b.anotherButton.onClick { - nav.navigate(R.id.loginChooserFragment, null, LoginActivity.navOptions) + nav.navigate(R.id.loginChooserFragment, null, activity.navOptions) } b.finishButton.onClick { @@ -86,7 +91,7 @@ class LoginSummaryFragment : Fragment(), CoroutineScope { val args = Bundle( "registrationAllowed" to b.registerMeSwitch.isChecked ) - nav.navigate(R.id.loginSyncFragment, args, LoginActivity.navOptions) + nav.navigate(R.id.loginSyncFragment, args, activity.navOptions) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt deleted file mode 100644 index 7a74dd76..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSummaryProfileAdapter.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.data.db.entity.Profile -import pl.szczodrzynski.edziennik.databinding.RowLoginProfileListItemBinding - -class LoginSummaryProfileAdapter( - val context: Context, - val items: List, - val onSelectionChanged: ((item: Item) -> Unit)? = null -) : RecyclerView.Adapter() { - - private val app by lazy { context.applicationContext as App } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflater = LayoutInflater.from(parent.context) - val view = RowLoginProfileListItemBinding.inflate(inflater, parent, false) - return ViewHolder(view) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val item = items[position] - val profile = item.profile - val b = holder.b - - b.textView.text = profile.name - b.checkBox.isChecked = item.isSelected - - val registerIcon = when (profile.loginStoreType) { - LOGIN_TYPE_MOBIDZIENNIK -> R.drawable.logo_mobidziennik - LOGIN_TYPE_LIBRUS -> R.drawable.logo_librus - LOGIN_TYPE_IDZIENNIK -> R.drawable.logo_idziennik - LOGIN_TYPE_VULCAN -> R.drawable.logo_vulcan - LOGIN_TYPE_EDUDZIENNIK -> R.drawable.logo_edudziennik - else -> null - } - if (registerIcon == null) - b.registerIcon.visibility = View.GONE - else { - b.registerIcon.visibility = View.VISIBLE - b.registerIcon.setImageResource(registerIcon) - } - - if (profile.isParent) { - b.accountType.setText(R.string.login_summary_account_parent) - } else { - b.accountType.setText(R.string.login_summary_account_child) - } - - val schoolYearName = "${profile.studentSchoolYearStart}/${profile.studentSchoolYearStart+1}" - b.textDetails.text = joinNotNullStrings( - " - ", - profile.studentClassName, - schoolYearName - ) - - b.root.onClick { - b.checkBox.trigger() - } - b.checkBox.setOnCheckedChangeListener { _, isChecked -> - item.isSelected = isChecked - onSelectionChanged?.invoke(item) - } - } - - override fun getItemCount() = items.size - - class ViewHolder(val b: RowLoginProfileListItemBinding) : RecyclerView.ViewHolder(b.root) - - class Item(val profile: Profile, var isSelected: Boolean = true) -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt index 49977dee..e13130a5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncErrorFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. + * Copyright (c) Kuba Szczodrzyński 2020-4-14. */ package pl.szczodrzynski.edziennik.ui.modules.login @@ -14,7 +14,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding +import pl.szczodrzynski.edziennik.databinding.LoginSyncErrorFragmentBinding import pl.szczodrzynski.edziennik.onClick import kotlin.coroutines.CoroutineContext @@ -25,18 +25,20 @@ class LoginSyncErrorFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginSyncErrorBinding + private lateinit var b: LoginSyncErrorFragmentBinding private val nav by lazy { activity.nav } private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + // local/private variables go here + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginSyncErrorBinding.inflate(inflater) + b = LoginSyncErrorFragmentBinding.inflate(inflater) return b.root } @@ -44,7 +46,7 @@ class LoginSyncErrorFragment : Fragment(), CoroutineScope { b.errorDetails.text = activity.lastError?.getStringReason(activity) activity.lastError = null b.nextButton.onClick { - nav.navigate(R.id.loginFinishFragment, arguments, LoginActivity.navOptions) + nav.navigate(R.id.loginFinishFragment, arguments, activity.navOptions) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt index 789bcb07..c0aab663 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginSyncFragment.kt @@ -1,3 +1,7 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-16. + */ + package pl.szczodrzynski.edziennik.ui.modules.login import android.os.Bundle @@ -19,9 +23,8 @@ import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskProgressEvent import pl.szczodrzynski.edziennik.data.api.events.ApiTaskStartedEvent -import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_DISABLED -import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED -import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncBinding +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.LoginSyncFragmentBinding import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt @@ -32,7 +35,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginSyncBinding + private lateinit var b: LoginSyncFragmentBinding private val nav: NavController by lazy { Navigation.findNavController(activity, R.id.nav_host_fragment) } private val job: Job = Job() @@ -45,7 +48,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope { activity = (getActivity() as LoginActivity?) ?: return null context ?: return null app = activity.application as App - b = FragmentLoginSyncBinding.inflate(inflater) + b = LoginSyncFragmentBinding.inflate(inflater) return b.root } @@ -56,9 +59,9 @@ class LoginSyncFragment : Fragment(), CoroutineScope { val registrationAllowed = arguments?.getBoolean("registrationAllowed") ?: false profiles.forEach { it.registration = if (registrationAllowed) - REGISTRATION_ENABLED + Profile.REGISTRATION_ENABLED else - REGISTRATION_DISABLED + Profile.REGISTRATION_DISABLED app.db.eventTypeDao().addDefaultTypes(activity, it.id) } @@ -84,13 +87,13 @@ class LoginSyncFragment : Fragment(), CoroutineScope { @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) { - nav.navigate(R.id.loginFinishFragment, finishArguments, LoginActivity.navOptions) + nav.navigate(R.id.loginFinishFragment, finishArguments, activity.navOptions) } @Subscribe(threadMode = ThreadMode.MAIN) fun onSyncProgressEvent(event: ApiTaskProgressEvent) { b.loginSyncProgressBar.progress = event.progress.roundToInt() - b.loginSyncProgressBar.isIndeterminate = event.progress < 0f + b.loginSyncProgressBar.isIndeterminate = event.progress <= 0f b.loginSyncSubtitle2.text = event.progressText } @@ -98,7 +101,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope { fun onSyncErrorEvent(event: ApiTaskErrorEvent) { EventBus.getDefault().removeStickyEvent(event) activity.error(event.error) - nav.navigate(R.id.loginSyncErrorFragment, finishArguments, LoginActivity.navOptions) + nav.navigate(R.id.loginSyncErrorFragment, finishArguments, activity.navOptions) } override fun onStart() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt deleted file mode 100644 index 4264416a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginTemplateFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_TEMPLATE -import pl.szczodrzynski.edziennik.databinding.FragmentLoginTemplateBinding -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginTemplateFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginTemplateFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginTemplateBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginTemplateBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN -> - b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password) - ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED -> - b.loginEmailLayout.error = getString(R.string.login_error_account_not_activated) - } - } - } - - b.helpButton.onClick { nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginEmailLayout.error = null - b.loginPasswordLayout.error = null - - val email = b.loginEmail.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val password = b.loginPassword.text?.toString() ?: "" - - if (email.isBlank()) { - b.loginEmailLayout.error = getString(R.string.login_error_no_email) - errors = true - } - if (password.isBlank()) { - b.loginPasswordLayout.error = getString(R.string.login_error_no_password) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginEmail.setText(email) - if (!"([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+".toRegex().matches(email)) { - b.loginEmailLayout.error = getString(R.string.login_error_incorrect_email) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_TEMPLATE, - "email" to email, - "password" to password - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt deleted file mode 100644 index 63ea9834..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanFragment.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) Kuba Szczodrzyński 2020-1-3. - */ - -package pl.szczodrzynski.edziennik.ui.modules.login - -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.fragment.app.Fragment -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial -import com.mikepenz.iconics.utils.colorInt -import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding -import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog -import pl.szczodrzynski.edziennik.utils.Utils -import java.util.* -import kotlin.coroutines.CoroutineContext - -class LoginVulcanFragment : Fragment(), CoroutineScope { - companion object { - private const val TAG = "LoginVulcanFragment" - } - - private lateinit var app: App - private lateinit var activity: LoginActivity - private lateinit var b: FragmentLoginVulcanBinding - private val nav by lazy { activity.nav } - - private val job: Job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - activity = (getActivity() as LoginActivity?) ?: return null - context ?: return null - app = activity.application as App - b = FragmentLoginVulcanBinding.inflate(inflater) - return b.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity.lastError?.let { error -> - activity.lastError = null - startCoroutineTimer(delayMillis = 100) { - when (error.errorCode) { - ERROR_LOGIN_VULCAN_INVALID_TOKEN -> - b.loginTokenLayout.error = getString(R.string.login_error_incorrect_token) - ERROR_LOGIN_VULCAN_EXPIRED_TOKEN -> - b.loginTokenLayout.error = getString(R.string.login_error_expired_token) - ERROR_LOGIN_VULCAN_INVALID_SYMBOL -> - b.loginSymbolLayout.error = getString(R.string.login_error_incorrect_symbol) - ERROR_LOGIN_VULCAN_INVALID_PIN -> - b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin) - } - } - } - - b.loginQrScan.setImageDrawable(IconicsDrawable(activity) - .icon(CommunityMaterial.Icon2.cmd_qrcode_scan) - .colorInt(Color.BLACK) - .sizeDp(72)) - b.loginQrScan.onClick { - QrScannerDialog(activity, { code -> - try { - val data = Utils.VulcanQrEncryptionUtils.decode(code) - "CERT#https?://.+?/([A-z]+)/mobile-api#([A-z0-9]+)#ENDCERT".toRegex().find(data)?.let { - b.loginToken.setText(it[2]) - b.loginSymbol.setText(it[1]) - if (b.loginPin.requestFocus()) { - activity.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - } - } - } - catch (_: Exception) {} - }) - } - - b.helpButton.onClick { nav.navigate(R.id.loginVulcanHelpFragment, null, LoginActivity.navOptions) } - b.backButton.onClick { nav.navigateUp() } - - b.loginButton.onClick { - var errors = false - - b.loginTokenLayout.error = null - b.loginSymbolLayout.error = null - b.loginPinLayout.error = null - - val token = b.loginToken.text?.toString()?.toUpperCase(Locale.ROOT) ?: "" - val symbol = b.loginSymbol.text?.toString()?.toLowerCase(Locale.ROOT) ?: "" - val pin = b.loginPin.text?.toString() ?: "" - - if (token.isBlank()) { - b.loginTokenLayout.error = getString(R.string.login_error_no_token) - errors = true - } - if (symbol.isBlank()) { - b.loginSymbolLayout.error = getString(R.string.login_error_no_symbol) - errors = true - } - if (pin.isBlank()) { - b.loginPinLayout.error = getString(R.string.login_error_no_pin) - errors = true - } - if (errors) return@onClick - - errors = false - - b.loginToken.setText(token) - b.loginSymbol.setText(symbol) - b.loginPin.setText(pin) - if (!"[A-Z0-9]{5,12}".toRegex().matches(token)) { - b.loginTokenLayout.error = getString(R.string.login_error_incorrect_token) - errors = true - } - if (!"[a-z0-9_-]+".toRegex().matches(symbol)) { - b.loginSymbolLayout.error = getString(R.string.login_error_incorrect_symbol) - errors = true - } - if (!"[a-z0-9_]+".toRegex().matches(pin)) { - b.loginPinLayout.error = getString(R.string.login_error_incorrect_pin) - errors = true - } - if (errors) return@onClick - - val args = Bundle( - "loginType" to LOGIN_TYPE_VULCAN, - "deviceToken" to token, - "deviceSymbol" to symbol, - "devicePin" to pin - ) - nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions) - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java deleted file mode 100644 index 0aa899ae..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginVulcanHelpFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.login; - -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanHelpBinding; - -public class LoginVulcanHelpFragment extends Fragment { - - private App app; - private NavController nav; - private FragmentLoginVulcanHelpBinding b; - private static final String TAG = "LoginVulcanHelp"; - - public LoginVulcanHelpFragment() { } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getActivity() != null) { - app = (App) getActivity().getApplicationContext(); - nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); - } - else { - return null; - } - b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_vulcan_help, container, false); - return b.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - assert getContext() != null; - assert getActivity() != null; - - b.backButton.setOnClickListener((v) -> nav.navigateUp()); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt new file mode 100644 index 00000000..cdb0e7b8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/ModeViewHolder.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LoginChooserModeItemBinding +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.LoginChooserAdapter +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo + +class ModeViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LoginChooserModeItemBinding = LoginChooserModeItemBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "ModeViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Mode, position: Int, adapter: LoginChooserAdapter) { + b.logo.setImageResource(item.icon) + b.name.setText(item.name) + if (item.hintText == null) { + b.description.isVisible = false + } + else { + b.description.isVisible = true + b.description.setText(item.hintText) + } + b.hint.isVisible = false + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt new file mode 100644 index 00000000..ea8a063d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/PlatformViewHolder.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import coil.api.load +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LoginPlatformItemBinding +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo +import pl.szczodrzynski.edziennik.ui.modules.login.LoginPlatformAdapter + +class PlatformViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LoginPlatformItemBinding = LoginPlatformItemBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "PlatformViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Platform, position: Int, adapter: LoginPlatformAdapter) { + b.logo.load(item.icon) + b.name.text = item.name + b.description.text = item.description + b.description.isVisible = item.description != null + b.screenshotButton.isVisible = false + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt new file mode 100644 index 00000000..921041c8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/viewholder/RegisterViewHolder.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-10. + */ + +package pl.szczodrzynski.edziennik.ui.modules.login.viewholder + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LoginChooserItemBinding +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.ui.modules.login.LoginChooserAdapter +import pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo + +class RegisterViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LoginChooserItemBinding = LoginChooserItemBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "RegisterViewHolder" + } + + override fun onBind(activity: AppCompatActivity, app: App, item: LoginInfo.Register, position: Int, adapter: LoginChooserAdapter) { + b.logo.setImageResource(item.registerLogo) + b.name.setText(item.registerName) + b.description.isVisible = false + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java index cc72c46c..3e3eac0e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/SettingsNewFragment.java @@ -55,6 +55,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog; import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog; import pl.szczodrzynski.edziennik.ui.dialogs.sync.NotificationFilterDialog; +import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity; import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Utils; import pl.szczodrzynski.edziennik.utils.models.Date; @@ -161,7 +162,7 @@ public class SettingsNewFragment extends MaterialAboutFragment { profileCardTitleItem = new MaterialAboutProfileItem( app.getProfile().getName(), - getString(R.string.settings_profile_subtitle_format, app.getProfile().getSubname()), + app.getProfile().getSubname(), getProfileDrawable() ); profileCardTitleItem.setOnClickAction(() -> { @@ -218,6 +219,20 @@ public class SettingsNewFragment extends MaterialAboutFragment { }) );*/ + items.add( + new MaterialAboutActionItem( + getString(R.string.settings_add_student_text), + getString(R.string.settings_add_student_subtext), + new IconicsDrawable(activity) + .icon(CommunityMaterial.Icon.cmd_account_plus_outline) + .size(IconicsSize.dp(iconSizeDp)) + .color(IconicsColor.colorInt(iconColor)) + ) + .setOnClickAction(() -> { + startActivity(new Intent(activity, LoginActivity.class)); + }) + ); + items.add( new MaterialAboutActionItem( getString(R.string.settings_profile_notifications_text), @@ -232,6 +247,20 @@ public class SettingsNewFragment extends MaterialAboutFragment { }) ); + items.add( + new MaterialAboutActionItem( + getString(R.string.settings_profile_remove_text), + getString(R.string.settings_profile_remove_subtext), + new IconicsDrawable(activity) + .icon(SzkolnyFont.Icon.szf_delete_empty_outline) + .size(IconicsSize.dp(iconSizeDp)) + .color(IconicsColor.colorInt(iconColor)) + ) + .setOnClickAction(() -> { + new ProfileRemoveDialog(activity, app.getProfile().getId(), app.getProfile().getName()); + }) + ); + items.add(getMoreItem(() -> addCardItems(CARD_PROFILE, getProfileCard(true)))); } else { @@ -253,20 +282,6 @@ public class SettingsNewFragment extends MaterialAboutFragment { })) ); - items.add( - new MaterialAboutActionItem( - getString(R.string.settings_profile_remove_text), - getString(R.string.settings_profile_remove_subtext), - new IconicsDrawable(activity) - .icon(SzkolnyFont.Icon.szf_delete_empty_outline) - .size(IconicsSize.dp(iconSizeDp)) - .color(IconicsColor.colorInt(iconColor)) - ) - .setOnClickAction(() -> { - new ProfileRemoveDialog(activity, app.getProfile().getId(), app.getProfile().getName()); - }) - ); - } return items; } diff --git a/app/src/main/res/drawable/logo_librus.png b/app/src/main/res/drawable/login_mode_librus_email.png similarity index 100% rename from app/src/main/res/drawable/logo_librus.png rename to app/src/main/res/drawable/login_mode_librus_email.png diff --git a/app/src/main/res/drawable/login_mode_librus_jst.png b/app/src/main/res/drawable/login_mode_librus_jst.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ea579c4edb85b9799343ca68867f5b11078b3d GIT binary patch literal 771 zcmV+e1N{7nP)IipGb6?MscD^c9=zPn@4h+MslJ`cAG|Wn@4k+M{=4) zaGOVR&7-;h|NogoZ_%s2mp^QyR(+E=XUUthvT>5IZji;4t*1e%7Lk*m6Zo~T}gku+oU>hHXSp~@z%!vFvP6LeBe zQ~m&w_Bi?~^VP?}F4{#s*w;rwTmKhp0005jNklZftL|Y9*QM79c&&ntcey7Vblz2Jz-u;8 zs(bzp{cJ0KQmTF7pjPvqpV)n~^$$8z{N%E;2JsyA|NN32Mi>%m|!6H zv0xyfOKBmHNM_G8hz+l*+Yyf%&OSY}zWfDAhXM=~paz5=xJkoT2uS&;*#|j9H7-t_ z#&T7P)9naqre*rd0j_Nb8yA%Kb002ovPDHLkV1i5q Ba(n;) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/logo_synergia.png b/app/src/main/res/drawable/login_mode_librus_synergia.png similarity index 100% rename from app/src/main/res/drawable/logo_synergia.png rename to app/src/main/res/drawable/login_mode_librus_synergia.png diff --git a/app/src/main/res/drawable/login_mode_mobidziennik_web.png b/app/src/main/res/drawable/login_mode_mobidziennik_web.png new file mode 100644 index 0000000000000000000000000000000000000000..5ab3d146420128b350f90a411f977405e22e9bf0 GIT binary patch literal 6008 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

!vb#hBrv>l!V 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 diff --git a/app/src/main/res/layout/activity_grades_editor.xml b/app/src/main/res/layout/activity_grades_editor.xml deleted file mode 100644 index 7e3a6998..00000000 --- a/app/src/main/res/layout/activity_grades_editor.xml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -