diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..733f463f --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 24f8c447..2f82ed79 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -11,7 +11,6 @@ - - + diff --git a/app/build.gradle b/app/build.gradle index 9bc32c1a..47b02774 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -203,8 +203,8 @@ dependencies { implementation 'com.google.android:flexbox:2.0.1' - implementation 'com.qifan.powerpermission:powerpermission:1.0.0' - implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.0.0' + implementation 'com.qifan.powerpermission:powerpermission:1.3.0' + implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.3.0' implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT' implementation 'pl.droidsonroids:jspoon:1.3.2' diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index d7181f1b..dbb51c25 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,9 +1,7 @@ -

Wersja 4.2.1, 2020-05-21

+

Wersja 4.3, 2020-08-26

    -
  • Poprawiona błędna godzina synchronizacji danych (przesunięcie w przód o 1-2h).
  • -
  • Vulcan: Naprawione logowanie.
  • -
  • Mobidziennik: Poprawione pobieranie szczęśliwego numerka.
  • -
  • Prymus: Naprawione logowane.
  • +
  • Dodana opcja automatycznej archiwizacji profilu na nowy rok szkolny.
  • +
  • Poprawione problemy z synchronizacją oraz mieszaniem się danych.


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 2b2922e6..e2a5a9c1 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0x43, 0xfc, 0x2d, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0x20, 0x98, 0x82, 0x66, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index bea90a98..65836b5e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -364,6 +364,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { if (!success) { EventBus.getDefault().post(ProfileListEmptyEvent()) } + else { + onSuccess(profile) + } } } fun profileSave() = profileSave(profile) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index 2443679b..7306109a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -42,6 +42,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.viewpager.widget.ViewPager import com.google.android.gms.security.ProviderInstaller import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject @@ -672,6 +673,16 @@ fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) { text = context.getString(resid, *formatArgs) } +fun MaterialAlertDialogBuilder.setTitle(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder { + setTitle(context.getString(resid, *formatArgs)) + return this +} + +fun MaterialAlertDialogBuilder.setMessage(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder { + setMessage(context.getString(resid, *formatArgs)) + return this +} + fun JsonObject(vararg properties: Pair): JsonObject { return JsonObject().apply { for (property in properties) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 3033f0f0..3932ce3b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -43,6 +43,7 @@ import pl.szczodrzynski.edziennik.data.api.events.* import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Metadata.* +import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent import pl.szczodrzynski.edziennik.sync.SyncWorker @@ -295,12 +296,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope { mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) - if (BuildConfig.VERSION_NAME.contains("nightly")) { - b.nightlyText.isVisible = true - b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + when { + BuildConfig.VERSION_NAME.contains("nightly") -> { + b.nightlyText.isVisible = true + b.nightlyText.text = "Nightly\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + } + BuildConfig.VERSION_NAME.contains("daily") -> { + b.nightlyText.isVisible = true + b.nightlyText.text = "Daily\n"+BuildConfig.VERSION_NAME.substringAfterLast(".") + } + BuildConfig.DEBUG -> { + b.nightlyText.isVisible = true + b.nightlyText.text = "Debug\n"+BuildConfig.VERSION_NAME + } + else -> b.nightlyText.isVisible = false } - else - b.nightlyText.isVisible = false navLoading = true @@ -399,7 +409,20 @@ class MainActivity : AppCompatActivity(), CoroutineScope { } app.db.profileDao().all.observe(this, Observer { profiles -> - drawer.setProfileList(profiles.filter { it.id >= 0 }.toMutableList()) + val allArchived = profiles.all { it.archived } + drawer.setProfileList(profiles.filter { it.id >= 0 && (!it.archived || allArchived) }.toMutableList()) + //prepend the archived profile if loaded + if (app.profile.archived && !allArchived) { + drawer.prependProfile(Profile( + id = app.profile.id, + loginStoreId = app.profile.loginStoreId, + loginStoreType = app.profile.loginStoreType, + name = app.profile.name, + subname = "Archiwum - ${app.profile.subname}" + ).also { + it.archived = true + }) + } drawer.currentProfile = App.profileId }) @@ -425,6 +448,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope { SyncWorker.scheduleNext(app) UpdateWorker.scheduleNext(app) + // if loaded profile is archived, switch to the up-to-date version of it + if (app.profile.archived) { + launch { + if (app.profile.archiveId != null) { + val profile = withContext(Dispatchers.IO) { + app.db.profileDao().getNotArchivedOf(app.profile.archiveId!!) + } + if (profile != null) + loadProfile(profile) + else + loadProfile(0) + } else { + loadProfile(0) + } + } + } + // APP BACKGROUND if (app.config.ui.appBackground != null) { try { @@ -565,6 +605,41 @@ class MainActivity : AppCompatActivity(), CoroutineScope { __/ | |__*/ fun syncCurrentFeature() { + if (app.profile.archived) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.profile_archived_title) + .setMessage( + R.string.profile_archived_text, + app.profile.studentSchoolYearStart, + app.profile.studentSchoolYearStart + 1 + ) + .setPositiveButton(R.string.ok, null) + .show() + swipeRefreshLayout.isRefreshing = false + return + } + if (app.profile.shouldArchive()) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.profile_archiving_title) + .setMessage( + R.string.profile_archiving_format, + app.profile.dateYearEnd.formattedString + ) + .setPositiveButton(R.string.ok, null) + .show() + } + if (app.profile.isBeforeYear()) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.profile_year_not_started_title) + .setMessage( + R.string.profile_year_not_started_format, + app.profile.dateSemester1Start.formattedString + ) + .setPositiveButton(R.string.ok, null) + .show() + swipeRefreshLayout.isRefreshing = false + return + } swipeRefreshLayout.isRefreshing = true Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show() val fragmentParam = when (navTargetId) { @@ -885,23 +960,51 @@ class MainActivity : AppCompatActivity(), CoroutineScope { fun loadProfile(id: Int) = loadProfile(id, navTargetId) fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments) - fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) { + fun loadProfile(profile: Profile) = loadProfile( + profile, + navTargetId, + null, + if (app.profile.archived) app.profile.id else null + ) + private fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) { if (App.profileId == id) { drawer.currentProfile = app.profile.id loadTarget(drawerSelection, arguments) return } + val previousArchivedId = if (app.profile.archived) app.profile.id else null app.profileLoad(id) { - MessagesFragment.pageSelection = -1 - - setDrawerItems() - // the drawer profile is updated automatically when the drawer item is clicked - // update it manually when switching profiles from other source - //if (drawer.currentProfile != app.profile.id) - drawer.currentProfile = app.profileId - loadTarget(drawerSelection, arguments) + loadProfile(it, drawerSelection, arguments, previousArchivedId) } } + private fun loadProfile(profile: Profile, drawerSelection: Int, arguments: Bundle?, previousArchivedId: Int?) { + App.profile = profile + MessagesFragment.pageSelection = -1 + + setDrawerItems() + + if (previousArchivedId != null) { + // prevents accidentally removing the first item if the archived profile is not shown + drawer.removeProfileById(previousArchivedId) + } + if (profile.archived) { + drawer.prependProfile(Profile( + id = profile.id, + loginStoreId = profile.loginStoreId, + loginStoreType = profile.loginStoreType, + name = profile.name, + subname = "Archiwum - ${profile.subname}" + ).also { + it.archived = true + }) + } + + // the drawer profile is updated automatically when the drawer item is clicked + // update it manually when switching profiles from other source + //if (drawer.currentProfile != app.profile.id) + drawer.currentProfile = app.profileId + loadTarget(drawerSelection, arguments) + } fun loadTarget(id: Int, arguments: Bundle? = null) { var loadId = id if (loadId == -1) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt index 4d3f0aca..e1a830b5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/EdziennikTask.kt @@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Teacher import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) { companion object { @@ -71,10 +72,24 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa private var edziennikInterface: EdziennikInterface? = null internal fun run(app: App, taskCallback: EdziennikCallback) { - if (profile?.archived == true) { - taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED)) - return + profile?.let { profile -> + if (profile.archived) { + d(TAG, "The profile $profileId is archived") + taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED)) + return + } + else if (profile.shouldArchive()) { + d(TAG, "The profile $profileId's year ended on ${profile.dateYearEnd}, archiving") + ProfileArchiver(app, profile) + } + if (profile.isBeforeYear()) { + d(TAG, "The profile $profileId's school year has not started yet; aborting sync") + cancel() + taskCallback.onCompleted() + return + } } + edziennikInterface = when (loginStore.type) { LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) @@ -108,6 +123,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa } override fun cancel() { + d(TAG, "Task ${toString()} cancelling...") edziennikInterface?.cancel() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt new file mode 100644 index 00000000..15da9765 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/ProfileArchiver.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-8-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik + +import android.content.Intent +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.Intent +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date + +class ProfileArchiver(val app: App, val profile: Profile) { + companion object { + private const val TAG = "ProfileArchiver" + } + + init { + if (profile.archiveId == null) + profile.archiveId = profile.id + d(TAG, "Processing ${profile.name}#${profile.id}, archiveId = ${profile.archiveId}") + + profile.archived = true + app.db.profileDao().add(profile) + //app.db.metadataDao().setAllSeen(profile.id, true) + app.db.notificationDao().clear(profile.id) + app.db.endpointTimerDao().clear(profile.id) + d(TAG, "Archived profile ${profile.id} saved") + profile.archived = false + + // guess the nearest school year + val today = Date.getToday() + profile.studentSchoolYearStart = when { + today.month <= profile.dateYearEnd.month -> today.year - 1 + else -> today.year + } + + // set default semester dates + profile.dateSemester1Start = Date(profile.studentSchoolYearStart, 9, 1) + profile.dateSemester2Start = Date(profile.studentSchoolYearStart + 1, 2, 1) + profile.dateYearEnd = Date(profile.studentSchoolYearStart + 1, 6, 30) + + val oldId = profile.id + val newId = (app.db.profileDao().lastId ?: profile.id) + 1 + profile.id = newId + profile.subname = "Nowy rok szkolny - ${profile.studentSchoolYearStart}" + profile.studentClassName = null + + d(TAG, "New profile ID for ${profile.name}: ${profile.id}") + + when (profile.loginStoreType) { + LOGIN_TYPE_LIBRUS -> { + profile.removeStudentData("isPremium") + profile.removeStudentData("pushDeviceId") + profile.removeStudentData("startPointsSemester1") + profile.removeStudentData("startPointsSemester2") + profile.removeStudentData("enablePointGrades") + profile.removeStudentData("enableDescriptiveGrades") + } + LOGIN_TYPE_MOBIDZIENNIK -> { + + } + LOGIN_TYPE_VULCAN -> { + // DataVulcan.isApiLoginValid() returns false so it will update the semester + profile.removeStudentData("currentSemesterEndDate") + profile.removeStudentData("studentSemesterId") + profile.removeStudentData("studentSemesterNumber") + profile.removeStudentData("semester1Id") + profile.removeStudentData("semester2Id") + profile.removeStudentData("studentClassId") + } + LOGIN_TYPE_IDZIENNIK -> { + profile.removeStudentData("schoolYearId") + } + LOGIN_TYPE_EDUDZIENNIK -> { + + } + LOGIN_TYPE_PODLASIE -> { + + } + } + + d(TAG, "Processed student data: ${profile.studentData}") + + app.db.profileDao().add(profile) + + if (app.profileId == oldId) { + val intent = Intent( + Intent.ACTION_MAIN, + "profileId" to newId + ) + app.sendBroadcast(intent) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt index 17bf771f..23d9d3b1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt @@ -110,7 +110,6 @@ class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStor override fun cancel() { d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt index e1fa6f00..e1550632 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt @@ -133,7 +133,6 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, override fun cancel() { d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt index 2c6ad0e7..a2fd4d03 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt @@ -70,6 +70,14 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { data.webSelectedRegister = registerId } + // for profiles created after archiving + data.schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let { + it[1].toIntOrNull() + } ?: data.schoolYearId + data.profile?.studentClassName = Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text) + .firstOrNull { it[1].toIntOrNull() == data.registerId } + ?.let { "${it[5]} ${it[6]}" } ?: data.profile?.studentClassName + data.profile?.let { profile -> Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also { val number = it[1].toIntOrNull() ?: return@also diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index 48f308f8..abf3ac5f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -157,7 +157,6 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va override fun cancel() { d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt index 94ab5ed1..3652f18a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusRecaptchaHelper.kt @@ -36,11 +36,14 @@ class LibrusRecaptchaHelper( } private var timeout: Job? = null + private var timedOut = false inner class WebViewClient : android.webkit.WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { timeout?.cancel() - onSuccess(url) + if (!timedOut) { + onSuccess(url) + } return true } } @@ -50,6 +53,7 @@ class LibrusRecaptchaHelper( webView.loadDataWithBaseURL(url, html, "text/html", "UTF-8", null) } timeout = startCoroutineTimer(delayMillis = 10000L) { + timedOut = true onTimeout() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt index ae5c6072..0e002576 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt @@ -130,7 +130,6 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto override fun cancel() { d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt index 30da6e02..d7ec441f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt @@ -134,7 +134,6 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore, override fun cancel() { Utils.d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt index 5843f927..843d36e9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt @@ -100,7 +100,6 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, override fun cancel() { d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt index e17d059f..9761343c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt @@ -194,7 +194,6 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va override fun cancel() { d(TAG, "Cancelled") data.cancel() - callback.onCompleted() } private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 343c0c8b..087b8ef8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDtTu0zYCV===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDQC7Eh97U===.$param2".sha256() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index 333967b0..aee8e3af 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* LibrusLesson::class, TimetableManual::class, Metadata::class -], version = 88) +], version = 89) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -173,7 +173,8 @@ abstract class AppDb : RoomDatabase() { Migration85(), Migration86(), Migration87(), - Migration88() + Migration88(), + Migration89() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt index 3225141e..6d1e799d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/ProfileDao.kt @@ -60,4 +60,10 @@ interface ProfileDao { @Query("UPDATE profiles SET empty = 0") fun setAllNotEmpty() + + @Query("SELECT * FROM profiles WHERE archiveId = :archiveId AND archived = 1") + fun getArchivesOf(archiveId: Int): List + + @Query("SELECT * FROM profiles WHERE archiveId = :archiveId AND archived = 0 ORDER BY profileId DESC LIMIT 1") + fun getNotArchivedOf(archiveId: Int): Profile? } 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 50ad82a7..eab4cb8b 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 @@ -28,7 +28,7 @@ import pl.szczodrzynski.navlib.getDrawableFromRes @Entity(tableName = "profiles", primaryKeys = ["profileId"]) open class Profile( @ColumnInfo(name = "profileId") - override val id: Int, + override var id: Int, /* needs to be var for ProfileArchiver */ val loginStoreId: Int, val loginStoreType: Int, @@ -64,6 +64,12 @@ open class Profile( var empty = true var archived = false + /** + * A unique ID matching [archived] profiles with current ones + * and vice-versa. + */ + var archiveId: Int? = null + var syncEnabled = true var enableSharedEvents = true var registration = REGISTRATION_UNSPECIFIED @@ -85,6 +91,23 @@ open class Profile( @delegate:Ignore val currentSemester by lazy { dateToSemester(Date.getToday()) } + fun shouldArchive(): Boolean { + // vulcan hotfix + if (dateYearEnd.month > 6) { + dateYearEnd.month = 6 + dateYearEnd.day = 30 + } + // fix for when versions <4.3 synced 2020/2021 year dates to older profiles during 2020 Jun-Aug + if (dateSemester1Start.year > studentSchoolYearStart) { + val diff = dateSemester1Start.year - studentSchoolYearStart + dateSemester1Start.year -= diff + dateSemester2Start.year -= diff + dateYearEnd.year -= diff + } + return Date.getToday() >= dateYearEnd && Date.getToday().year > studentSchoolYearStart + } + fun isBeforeYear() = Date.getToday() < dateSemester1Start + var disabledNotifications: List? = null var lastReceiversSync: Long = 0 @@ -106,14 +129,18 @@ open class Profile( get() = accountName != null override fun getImageDrawable(context: Context): Drawable { + if (archived) { + return context.getDrawableFromRes(pl.szczodrzynski.edziennik.R.drawable.profile_archived).also { + it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER) + } + } if (!image.isNullOrEmpty()) { try { - if (image?.endsWith(".gif", true) == true) { - return GifDrawable(image ?: "") - } - else { - return RoundedBitmapDrawableFactory.create(context.resources, image ?: "") + return if (image?.endsWith(".gif", true) == true) { + GifDrawable(image ?: "") + } else { + RoundedBitmapDrawableFactory.create(context.resources, image ?: "") //return Drawable.createFromPath(image ?: "") ?: throw Exception() } } @@ -125,9 +152,13 @@ open class Profile( return context.getDrawableFromRes(R.drawable.profile).also { it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER) } - } + override fun getImageHolder(context: Context): ImageHolder { + if (archived) { + return ImageHolder(pl.szczodrzynski.edziennik.R.drawable.profile_archived, colorFromName(name)) + } + return if (!image.isNullOrEmpty()) { try { ProfileImageHolder(image ?: "") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration89.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration89.kt new file mode 100644 index 00000000..ce665d1f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration89.kt @@ -0,0 +1,10 @@ +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration89 : Migration(88, 89) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE profiles ADD COLUMN archiveId INTEGER DEFAULT NULL;") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt index bad9fd9f..72b9f9f5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt @@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment +import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.fslogin.decode import kotlin.coroutines.CoroutineContext @@ -62,6 +63,21 @@ class LabPageFragment : LazyFragment(), CoroutineScope { app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") } + b.unarchive.onClick { + app.profile.archived = false + app.profile.archiveId = null + app.profileSave() + } + + val profiles = app.db.profileDao().allNow + b.profile.clear() + b.profile += profiles.map { TextInputDropDown.Item(it.id.toLong(), "${it.id} ${it.name} archived ${it.archived}", tag = it) } + b.profile.select(app.profileId.toLong()) + b.profile.setOnChangeListener { + activity.loadProfile(it.id.toInt()) + return@setOnChangeListener true + } + val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) startCoroutineTimer(500L, 300L) { val text = app.cookieJar.sessionCookies diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CardItemTouchHelperCallback.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CardItemTouchHelperCallback.kt index 66908998..34621474 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CardItemTouchHelperCallback.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/CardItemTouchHelperCallback.kt @@ -33,7 +33,7 @@ class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, priv } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - removeCard(viewHolder.adapterPosition) + removeCard(viewHolder.adapterPosition, cardAdapter) cardAdapter.items.removeAt(viewHolder.adapterPosition) cardAdapter.notifyItemRemoved(viewHolder.adapterPosition) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt index 181dadc1..8d276a82 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/HomeFragment.kt @@ -28,10 +28,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog -import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeEventsCard -import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeGradesCard -import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard -import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard +import pl.szczodrzynski.edziennik.ui.modules.home.cards.* import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem @@ -44,8 +41,8 @@ class HomeFragment : Fragment(), CoroutineScope { fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter): Boolean { val fromCard = cardAdapter.items[fromPosition] val toCard = cardAdapter.items[toPosition] - if (fromCard.id == 100 || toCard.id == 100) { - // debug card is not swappable + if (fromCard.id >= 100 || toCard.id >= 100) { + // debug & archive cards are not swappable return false } cardAdapter.items[fromPosition] = cardAdapter.items[toPosition] @@ -60,10 +57,16 @@ class HomeFragment : Fragment(), CoroutineScope { return true } - fun removeCard(position: Int) { + fun removeCard(position: Int, cardAdapter: HomeCardAdapter) { val homeCards = App.config.forProfile().ui.homeCards.toMutableList() if (position >= homeCards.size) return + val card = cardAdapter.items[position] + if (card.id >= 100) { + // debug & archive cards are not removable + cardAdapter.notifyDataSetChanged() + return + } homeCards.removeAt(position) App.config.forProfile().ui.homeCards = homeCards } @@ -160,6 +163,8 @@ class HomeFragment : Fragment(), CoroutineScope { } //if (App.devMode) // items += HomeDebugCard(100, app, activity, this, app.profile) + if (app.profile.archived) + items.add(0, HomeArchiveCard(101, app, activity, this, app.profile)) val adapter = HomeCardAdapter(items) val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout)) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeArchiveCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeArchiveCard.kt new file mode 100644 index 00000000..17fc89e4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeArchiveCard.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-8-25. + */ + +package pl.szczodrzynski.edziennik.ui.modules.home.cards + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.view.plusAssign +import androidx.core.view.setMargins +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.CardHomeArchiveBinding +import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard +import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter +import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment +import kotlin.coroutines.CoroutineContext + +class HomeArchiveCard( + override val id: Int, + val app: App, + val activity: MainActivity, + val fragment: HomeFragment, + val profile: Profile +) : HomeCard, CoroutineScope { + companion object { + private const val TAG = "HomeArchiveCard" + } + + private var job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { + holder.root.removeAllViews() + val b = CardHomeArchiveBinding.inflate(LayoutInflater.from(holder.root.context)) + b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + setMargins(8.dp) + } + holder.root += b.root + + b.homeArchiveText.setText( + R.string.home_archive_text, + profile.studentSchoolYearStart, + profile.studentSchoolYearStart + 1 + ) + + b.homeArchiveClose.onClick { + launch { + val profile = profile.archiveId?.let { + withContext(Dispatchers.IO) { + app.db.profileDao().getNotArchivedOf(it) + } + } + if (profile == null) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.home_archive_close_no_target_title) + .setMessage(R.string.home_archive_close_no_target_text, this@HomeArchiveCard.profile.name) + .setPositiveButton(R.string.ok) { _, _ -> + activity.drawer.profileSelectionOpen() + activity.drawer.open() + } + .show() + return@launch + } + activity.loadProfile(profile) + } + } + + holder.root.onClick { + activity.loadTarget(MainActivity.DRAWER_ITEM_AGENDA) + } + } + + override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit +} diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml new file mode 100644 index 00000000..a61f71cc --- /dev/null +++ b/app/src/main/res/drawable/ic_archive.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/app/src/main/res/drawable/profile_archived.xml b/app/src/main/res/drawable/profile_archived.xml new file mode 100644 index 00000000..c816dcc8 --- /dev/null +++ b/app/src/main/res/drawable/profile_archived.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/layout/card_grades.xml b/app/src/main/res/layout/card_grades.xml deleted file mode 100644 index 249a971f..00000000 --- a/app/src/main/res/layout/card_grades.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - -