From 80dcd9aa69badef2d6041f59d7f19a002a6c0677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 18 Sep 2021 16:36:17 +0200 Subject: [PATCH] [API] Refactor register availability checking module. --- .../java/pl/szczodrzynski/edziennik/App.kt | 1 + .../szczodrzynski/edziennik/MainActivity.kt | 57 ++++------ .../szczodrzynski/edziennik/config/Config.kt | 5 + .../data/api/edziennik/EdziennikTask.kt | 34 ++---- .../api/events/RegisterAvailabilityEvent.kt | 6 +- .../edziennik/data/db/entity/Profile.kt | 2 +- .../data/firebase/SzkolnyAppFirebase.kt | 2 +- .../ui/dialogs/RegisterUnavailableDialog.kt | 2 - .../edziennik/ui/modules/home/HomeFragment.kt | 5 +- .../home/cards/HomeAvailabilityCard.kt | 10 +- .../ui/modules/login/LoginChooserFragment.kt | 60 +++-------- .../utils/managers/AvailabilityManager.kt | 100 ++++++++++++++++++ 12 files changed, 162 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index 71e6c7e6..4db63386 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -71,6 +71,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { val permissionManager by lazy { PermissionManager(this) } val attendanceManager by lazy { AttendanceManager(this) } val buildManager by lazy { BuildManager(this) } + val availabilityManager by lazy { AvailabilityManager(this) } val db get() = App.db diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 2cd4f7b7..b7540b44 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -85,6 +85,8 @@ import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.dpToPx +import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager +import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.navlib.* @@ -634,45 +636,23 @@ class MainActivity : AppCompatActivity(), CoroutineScope { return } - app.profile.registerName?.let { registerName -> - var status = app.config.sync.registerAvailability[registerName] - if (status == null || status.nextCheckAt < currentTimeUnix()) { - val api = SzkolnyApi(app) - val result = withContext(Dispatchers.IO) { - return@withContext api.runCatching({ - val availability = getRegisterAvailability() - app.config.sync.registerAvailability = availability - availability[registerName] - }, onError = { - if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) { - return@withContext false - } - return@withContext it - }) - } - - when (result) { - false -> { - Toast.makeText(this@MainActivity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() - return@let - } - is Throwable -> { - errorSnackbar.addError(result.toApiError(TAG)).show() - return - } - is RegisterAvailabilityStatus -> { - status = result - } - } - } - - if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) { + val error = withContext(Dispatchers.IO) { + app.availabilityManager.check(app.profile) + } + when (error?.type) { + Type.NOT_AVAILABLE -> { swipeRefreshLayout.isRefreshing = false loadTarget(DRAWER_ITEM_HOME) - if (status != null) - RegisterUnavailableDialog(this, status) + RegisterUnavailableDialog(this, error.status!!) return } + Type.API_ERROR -> { + errorSnackbar.addError(error.apiError!!).show() + return + } + Type.NO_API_ACCESS -> { + Toast.makeText(this, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() + } } swipeRefreshLayout.isRefreshing = true @@ -699,10 +679,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope { @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) { EventBus.getDefault().removeStickyEvent(event) - app.profile.registerName?.let { registerName -> - event.data[registerName]?.let { - RegisterUnavailableDialog(this, it) - } + val error = app.availabilityManager.check(app.profile, cacheOnly = true) + if (error != null) { + RegisterUnavailableDialog(this, error.status!!) } } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt index 0274912f..8c9270f2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -125,6 +125,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig { get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert } set(value) { set("apiInvalidCert", value); mApiInvalidCert = value } + private var mApiAvailabilityCheck: Boolean? = null + var apiAvailabilityCheck: Boolean + get() { mApiAvailabilityCheck = mApiAvailabilityCheck ?: values.get("apiAvailabilityCheck", true); return mApiAvailabilityCheck ?: true } + set(value) { set("apiAvailabilityCheck", value); mApiAvailabilityCheck = value } + private var rawEntries: List = db.configDao().getAllNow() private val profileConfigs: HashMap = hashMapOf() init { 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 e3692c33..ab27dda9 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 @@ -18,7 +18,6 @@ import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError -import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.task.IApiTask import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.data.db.entity.Profile @@ -27,6 +26,7 @@ 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 +import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) { companion object { @@ -90,35 +90,21 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa return } - profile.registerName?.also { registerName -> - var status = app.config.sync.registerAvailability[registerName] - if (status == null || status.nextCheckAt < currentTimeUnix()) { - val api = SzkolnyApi(app) - api.runCatching({ - val availability = getRegisterAvailability() - app.config.sync.registerAvailability = availability - status = availability[registerName] - }, onError = { - val apiError = it.toApiError(TAG) - if (apiError.errorCode == ERROR_API_INVALID_SIGNATURE) { - return@also - } - taskCallback.onError(apiError) - return - }) - } - - if (status?.available != true - || status?.minVersionCode ?: BuildConfig.VERSION_CODE > BuildConfig.VERSION_CODE) { + val error = app.availabilityManager.check(profile) + when (error?.type) { + Type.NOT_AVAILABLE -> { if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { - EventBus.getDefault().postSticky( - RegisterAvailabilityEvent(app.config.sync.registerAvailability) - ) + EventBus.getDefault().postSticky(RegisterAvailabilityEvent()) } cancel() taskCallback.onCompleted() return } + Type.API_ERROR -> { + taskCallback.onError(error.apiError!!) + return + } + else -> return@let } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt index fe66c4b3..2a3531df 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/RegisterAvailabilityEvent.kt @@ -4,8 +4,4 @@ package pl.szczodrzynski.edziennik.data.api.events -import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus - -data class RegisterAvailabilityEvent( - val data: Map< String, RegisterAvailabilityStatus> -) +class RegisterAvailabilityEvent() 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 b3586a79..9c2552a6 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 @@ -140,7 +140,7 @@ open class Profile( LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" LOGIN_TYPE_PODLASIE -> "podlasie" LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" - else -> null + else -> "unknown" } override fun getImageDrawable(context: Context): Drawable { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index 1d563c34..111fef4a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -60,7 +60,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: ) ?: return@launch app.config.sync.registerAvailability = data if (EventBus.getDefault().hasSubscriberForEvent(RegisterAvailabilityEvent::class.java)) { - EventBus.getDefault().postSticky(RegisterAvailabilityEvent(data)) + EventBus.getDefault().postSticky(RegisterAvailabilityEvent()) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt index b9e733b8..10853812 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/RegisterUnavailableDialog.kt @@ -42,8 +42,6 @@ class RegisterUnavailableDialog( init { run { if (activity.isFinishing) return@run - if (status.available && status.minVersionCode <= BuildConfig.VERSION_CODE) - return@run onShowListener?.invoke(TAG) app = activity.applicationContext as App 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 944c60e8..5d5cbc45 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 @@ -163,10 +163,9 @@ class HomeFragment : Fragment(), CoroutineScope { if (app.profile.archived) items.add(0, HomeArchiveCard(101, app, activity, this, app.profile)) - val status = app.config.sync.registerAvailability[app.profile.registerName] + val status = app.availabilityManager.check(app.profile, cacheOnly = true)?.status val update = app.config.update - if (update != null && update.versionCode > BuildConfig.VERSION_CODE - || status != null && (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE)) { + if (update != null && update.versionCode > BuildConfig.VERSION_CODE || status?.userMessage != null) { items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile)) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt index fd54748e..613658f4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeAvailabilityCard.kt @@ -50,7 +50,8 @@ class HomeAvailabilityCard( } holder.root += b.root - val status = app.config.sync.registerAvailability[profile.registerName] + val error = app.availabilityManager.check(profile, cacheOnly = true) + val status = error?.status val update = app.config.update if (update == null && status == null) @@ -58,7 +59,8 @@ class HomeAvailabilityCard( var onInfoClick = { _: View -> } - if (status != null && !status.available && status.userMessage != null) { + // show "register unavailable" only when disabled + if (status?.userMessage != null) { b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityUpdate.isVisible = false @@ -69,6 +71,7 @@ class HomeAvailabilityCard( RegisterUnavailableDialog(activity, status) } } + // show "update available" when available OR version too old for the register else if (update != null && update.versionCode > BuildConfig.VERSION_CODE) { b.homeAvailabilityTitle.setText(R.string.home_availability_title) b.homeAvailabilityText.setText(R.string.home_availability_text, update.versionName) @@ -78,6 +81,9 @@ class HomeAvailabilityCard( UpdateAvailableDialog(activity, update) } } + else { + b.root.isVisible = false + } b.homeAvailabilityUpdate.onClick { if (update == null) 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 858b4a01..71a5b804 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 @@ -26,13 +26,12 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.* -import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi -import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type import pl.szczodrzynski.edziennik.utils.models.Date import kotlin.coroutines.CoroutineContext @@ -269,52 +268,23 @@ class LoginChooserFragment : Fragment(), CoroutineScope { } private suspend fun checkAvailability(loginType: Int): Boolean { - when (loginType) { - LOGIN_TYPE_LIBRUS -> "librus" - LOGIN_TYPE_VULCAN -> "vulcan" - LOGIN_TYPE_IDZIENNIK -> "idziennik" - LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" - LOGIN_TYPE_PODLASIE -> "podlasie" - LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" - else -> null - }?.let { registerName -> - var status = app.config.sync.registerAvailability[registerName] - if (status == null || status.nextCheckAt < currentTimeUnix()) { - val api = SzkolnyApi(app) - val result = withContext(Dispatchers.IO) { - return@withContext api.runCatching({ - val availability = getRegisterAvailability() - app.config.sync.registerAvailability = availability - availability[registerName] - }, onError = { - if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) { - return@withContext false - } - return@withContext it - }) - } + val error = withContext(Dispatchers.IO) { + app.availabilityManager.check(loginType) + } ?: return true - when (result) { - false -> { - Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() - return@let - } - is Throwable -> { - activity.errorSnackbar.addError(result.toApiError(TAG)).show() - return false - } - is RegisterAvailabilityStatus -> { - status = result - } - } + return when (error.type) { + Type.NOT_AVAILABLE -> { + RegisterUnavailableDialog(activity, error.status!!) + false } - - if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) { - if (status != null) - RegisterUnavailableDialog(activity, status) - return false + Type.API_ERROR -> { + activity.errorSnackbar.addError(error.apiError!!).show() + false + } + Type.NO_API_ACCESS -> { + Toast.makeText(activity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show() + true } } - return true } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt new file mode 100644 index 00000000..0c93468e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/AvailabilityManager.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) Kuba SzczodrzyƄski 2021-9-18. + */ + +package pl.szczodrzynski.edziennik.utils.managers + +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.toApiError + +class AvailabilityManager(val app: App) { + companion object { + private const val TAG = "AvailabilityManager" + } + + private val api = SzkolnyApi(app) + + data class Error( + val type: Type, + val status: RegisterAvailabilityStatus?, + val apiError: ApiError? + ) { + companion object { + fun notAvailable(status: RegisterAvailabilityStatus) = + Error(Type.NOT_AVAILABLE, status, null) + + fun apiError(apiError: ApiError) = + Error(Type.API_ERROR, null, apiError) + + fun noApiAccess() = + Error(Type.NO_API_ACCESS, null, null) + } + + enum class Type { + NOT_AVAILABLE, + API_ERROR, + NO_API_ACCESS, + } + } + + fun check(profile: Profile, cacheOnly: Boolean = false): Error? { + return check(profile.registerName, cacheOnly) + } + + fun check(loginType: Int, cacheOnly: Boolean = false): Error? { + val registerName = when (loginType) { + LOGIN_TYPE_LIBRUS -> "librus" + LOGIN_TYPE_VULCAN -> "vulcan" + LOGIN_TYPE_IDZIENNIK -> "idziennik" + LOGIN_TYPE_MOBIDZIENNIK -> "mobidziennik" + LOGIN_TYPE_PODLASIE -> "podlasie" + LOGIN_TYPE_EDUDZIENNIK -> "edudziennik" + else -> "unknown" + } + return check(registerName, cacheOnly) + } + + fun check(registerName: String, cacheOnly: Boolean = false): Error? { + if (!app.config.apiAvailabilityCheck) + return null + val status = app.config.sync.registerAvailability[registerName] + if (status != null && status.nextCheckAt > currentTimeUnix()) { + return reportStatus(status) + } + if (cacheOnly) { + return reportStatus(status) + } + + return try { + val availability = api.getRegisterAvailability() + app.config.sync.registerAvailability = availability + reportStatus(availability[registerName]) + } catch (e: Throwable) { + reportApiError(e) + } + } + + private fun reportStatus(status: RegisterAvailabilityStatus?): Error? { + if (status == null) + return null + if (!status.available || status.minVersionCode > BuildConfig.VERSION_CODE) + return Error.notAvailable(status) + return null + } + + private fun reportApiError(throwable: Throwable): Error { + val apiError = throwable.toApiError(TAG) + if (apiError.errorCode == ERROR_API_INVALID_SIGNATURE) { + app.config.sync.registerAvailability = mapOf() + return Error.noApiAccess() + } + return Error.apiError(apiError) + } +}